├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── core │ │ ├── __init__.py │ │ ├── test_runner.py │ │ └── test_utils.py │ ├── more │ │ ├── __init__.py │ │ ├── base │ │ │ ├── __init__.py │ │ │ └── database │ │ │ │ └── __init__.py │ │ ├── linux │ │ │ ├── __init__.py │ │ │ └── networking │ │ │ │ ├── __init__.py │ │ │ │ └── test_hosts.py │ │ ├── centos │ │ │ ├── __init__.py │ │ │ ├── vcs │ │ │ │ ├── __init__.py │ │ │ │ └── test_git.py │ │ │ ├── database │ │ │ │ ├── __init__.py │ │ │ │ ├── fixtures.py │ │ │ │ └── test_postgresql.py │ │ │ ├── package │ │ │ │ └── __init__.py │ │ │ ├── users │ │ │ │ └── __init__.py │ │ │ ├── utils │ │ │ │ ├── __init__.py │ │ │ │ └── test_hostname.py │ │ │ └── messaging │ │ │ │ └── __init__.py │ │ └── debian │ │ │ ├── __init__.py │ │ │ ├── vcs │ │ │ ├── __init__.py │ │ │ └── test_git.py │ │ │ ├── web │ │ │ ├── __init__.py │ │ │ ├── test_tornado.py │ │ │ └── test_apache.py │ │ │ ├── cache │ │ │ ├── __init__.py │ │ │ └── test_varnish.py │ │ │ ├── database │ │ │ ├── __init__.py │ │ │ ├── test_redis.py │ │ │ ├── test_postgresql.py │ │ │ └── fixtures.py │ │ │ ├── package │ │ │ ├── __init__.py │ │ │ ├── test_gem.py │ │ │ └── test_npm.py │ │ │ ├── security │ │ │ ├── __init__.py │ │ │ ├── test_ufw.py │ │ │ ├── test_apparmor.py │ │ │ └── test_selinux.py │ │ │ ├── users │ │ │ ├── __init__.py │ │ │ ├── test_passwd_utils.py │ │ │ └── test_ssh.py │ │ │ ├── messaging │ │ │ └── __init__.py │ │ │ ├── monitoring │ │ │ └── __init__.py │ │ │ └── programming │ │ │ ├── __init__.py │ │ │ ├── test_ruby.py │ │ │ ├── test_php.py │ │ │ └── test_nodejs.py │ ├── tools │ │ ├── __init__.py │ │ └── helpers.py │ └── fixtures │ │ ├── some_template.txt │ │ ├── for_testing.txt │ │ ├── test_public_key │ │ └── test_private_key.pem ├── end_to_end │ ├── __init__.py │ ├── Vagrantfile │ ├── provy-e2e-key.pub │ ├── test.py │ ├── files │ │ ├── website.py │ │ ├── website │ │ └── nginx.conf │ ├── provy-e2e-key │ ├── Makefile │ └── provyfile.py └── functional │ ├── __init__.py │ ├── core │ ├── __init__.py │ └── test_runner.py │ └── fixtures │ ├── __init__.py │ └── provyfile.py ├── provy ├── more │ ├── linux │ │ ├── __init__.py │ │ └── networking │ │ │ ├── __init__.py │ │ │ └── hosts.py │ ├── __init__.py │ ├── base │ │ ├── __init__.py │ │ └── database │ │ │ └── __init__.py │ ├── centos │ │ ├── users │ │ │ └── __init__.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── hostname.py │ │ ├── networking │ │ │ ├── __init__.py │ │ │ └── hosts.py │ │ ├── messaging │ │ │ └── __init__.py │ │ ├── vcs │ │ │ ├── __init__.py │ │ │ └── git.py │ │ ├── package │ │ │ └── __init__.py │ │ ├── database │ │ │ ├── __init__.py │ │ │ └── postgresql.py │ │ └── __init__.py │ └── debian │ │ ├── web │ │ ├── templates │ │ │ ├── local.settings.template │ │ │ ├── rails-nginx.template │ │ │ ├── rails.nginx.conf.template │ │ │ └── website.init.template │ │ ├── __init__.py │ │ └── tornado.py │ │ ├── networking │ │ ├── __init__.py │ │ └── hosts.py │ │ ├── users │ │ ├── __init__.py │ │ ├── passwd_utils.py │ │ └── ssh.py │ │ ├── monitoring │ │ ├── __init__.py │ │ └── templates │ │ │ ├── supervisord.init.template │ │ │ └── supervisord.conf.template │ │ ├── messaging │ │ └── __init__.py │ │ ├── vcs │ │ ├── __init__.py │ │ └── git.py │ │ ├── programming │ │ ├── __init__.py │ │ ├── php.py │ │ └── ruby.py │ │ ├── cache │ │ ├── __init__.py │ │ └── templates │ │ │ └── memcached.conf.template │ │ ├── security │ │ └── __init__.py │ │ ├── database │ │ ├── __init__.py │ │ ├── redis.py │ │ └── postgresql.py │ │ ├── package │ │ ├── __init__.py │ │ └── npm.py │ │ └── __init__.py ├── core │ ├── errors.py │ ├── __init__.py │ ├── utils.py │ └── runner.py ├── __init__.py └── console.py ├── docs └── source │ ├── whos-using.rst │ ├── api │ ├── modules.rst │ ├── provy.more.linux.rst │ ├── provy.more.base.rst │ ├── provy.more.rst │ ├── provy.more.centos.vcs.rst │ ├── provy.more.debian.vcs.rst │ ├── provy.more.centos.users.rst │ ├── provy.more.centos.utils.rst │ ├── provy.more.base.database.rst │ ├── provy.more.linux.networking.rst │ ├── provy.more.centos.messaging.rst │ ├── provy.more.centos.networking.rst │ ├── provy.more.debian.networking.rst │ ├── provy.more.debian.monitoring.rst │ ├── provy.rst │ ├── provy.more.centos.rst │ ├── provy.more.debian.users.rst │ ├── provy.more.centos.package.rst │ ├── provy.more.debian.cache.rst │ ├── provy.more.centos.database.rst │ ├── provy.more.debian.rst │ ├── provy.more.debian.programming.rst │ ├── provy.core.rst │ ├── provy.more.debian.security.rst │ ├── provy.more.debian.database.rst │ ├── provy.more.debian.web.rst │ └── provy.more.debian.package.rst │ ├── images │ ├── gg.png │ ├── logo.png │ ├── github.png │ ├── python.png │ ├── provyfile.png │ ├── centos-logo.png │ ├── debian-logo.png │ ├── provy-logo.png │ ├── ubuntu-logo.png │ ├── provy_sample.png │ └── django-nginx-recipe.png │ ├── themes │ └── provy │ │ ├── static │ │ └── provy.css_t │ │ └── theme.conf │ ├── recipes.rst │ ├── roles-docs.rst │ ├── what-are-roles.rst │ ├── changelog.rst │ ├── ideas.rst │ ├── using-roles.rst │ ├── supported-os.rst │ ├── running.rst │ ├── whats-provy.rst │ ├── custom-files.rst │ ├── installing.rst │ ├── provyfile.rst │ ├── recipes │ └── django-1-server.rst │ ├── index.rst │ └── contributing.rst ├── MANIFEST.in ├── REQUIREMENTS.docs ├── REQUIREMENTS ├── .gitignore ├── .coveragerc ├── .travis.yml ├── vagrant └── Vagrantfile ├── Makefile ├── LICENSE ├── README.mkd ├── setup.py └── docs.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /provy/more/linux/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/end_to_end/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/base/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/linux/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/functional/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/base/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/package/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/cache/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/package/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/security/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/centos/messaging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/messaging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/debian/programming/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/more/linux/networking/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/fixtures/some_template.txt: -------------------------------------------------------------------------------- 1 | foo={{ foo }} -------------------------------------------------------------------------------- /docs/source/whos-using.rst: -------------------------------------------------------------------------------- 1 | Who's using provy? 2 | ================== -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune dist 2 | prune build 3 | recursive-include provy *.template 4 | -------------------------------------------------------------------------------- /docs/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | provy 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 8 6 | 7 | provy 8 | -------------------------------------------------------------------------------- /docs/source/images/gg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/gg.png -------------------------------------------------------------------------------- /docs/source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/logo.png -------------------------------------------------------------------------------- /docs/source/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/github.png -------------------------------------------------------------------------------- /docs/source/images/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/python.png -------------------------------------------------------------------------------- /docs/source/images/provyfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/provyfile.png -------------------------------------------------------------------------------- /docs/source/images/centos-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/centos-logo.png -------------------------------------------------------------------------------- /docs/source/images/debian-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/debian-logo.png -------------------------------------------------------------------------------- /docs/source/images/provy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/provy-logo.png -------------------------------------------------------------------------------- /docs/source/images/ubuntu-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/ubuntu-logo.png -------------------------------------------------------------------------------- /docs/source/images/provy_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/provy_sample.png -------------------------------------------------------------------------------- /docs/source/images/django-nginx-recipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-provy/provy/HEAD/docs/source/images/django-nginx-recipe.png -------------------------------------------------------------------------------- /REQUIREMENTS.docs: -------------------------------------------------------------------------------- 1 | fabric 2 | flake8 3 | ipdb 4 | jinja2 5 | nose 6 | mock 7 | coverage 8 | yanc 9 | xtraceback 10 | configobj 11 | pygments 12 | sphinx 13 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.linux.rst: -------------------------------------------------------------------------------- 1 | linux Package 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | provy.more.linux.networking 10 | 11 | -------------------------------------------------------------------------------- /provy/core/errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class ConfigurationError(RuntimeError): 6 | '''Raised when there's a configuration error in the provyfile.''' 7 | -------------------------------------------------------------------------------- /REQUIREMENTS: -------------------------------------------------------------------------------- 1 | fabric 2 | flake8 3 | ipdb 4 | jinja2 5 | nose 6 | mock 7 | coverage 8 | yanc 9 | xtraceback 10 | configobj 11 | pygments 12 | sphinx 13 | coveralls 14 | pylint 15 | requests 16 | -e . 17 | -------------------------------------------------------------------------------- /tests/unit/fixtures/for_testing.txt: -------------------------------------------------------------------------------- 1 | Django 2 | yolk==0.4.1 3 | http://www.satchmoproject.com/snapshots/trml2pdf-1.2.tar.gz 4 | -e hg+http://bitbucket.org/bkroeze/django-threaded-multihost/#egg=django-threaded-multihost 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | build 4 | dist 5 | pyVows.egg-info 6 | vagrant/.vagrant 7 | provy.egg-info 8 | *.swn 9 | *.swo 10 | .vagrant 11 | .coverage 12 | coverate.xml 13 | .idea/ 14 | setup.cfg 15 | cover/ 16 | -------------------------------------------------------------------------------- /provy/more/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | provy's specialized Roles reside in this namespace. 6 | This namespace is divided among the different operating systems provy supports. 7 | ''' 8 | -------------------------------------------------------------------------------- /provy/more/base/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are abstract base roles, which contain common behavior among distribuitions. 7 | ''' 8 | 9 | from provy.more.base.database import * 10 | -------------------------------------------------------------------------------- /provy/more/centos/users/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable user management in CentOS distributions. 7 | ''' 8 | 9 | from provy.more.centos.users.user import UserRole 10 | -------------------------------------------------------------------------------- /docs/source/themes/provy/static/provy.css_t: -------------------------------------------------------------------------------- 1 | @import url("default.css"); 2 | @import url("https://fonts.googleapis.com/css?family=Maven+Pro|Lobster&v2"); 3 | 4 | a:link { 5 | text-decoration: underline; 6 | } 7 | 8 | th { 9 | background-color: rgb(254, 213, 187); 10 | } 11 | -------------------------------------------------------------------------------- /provy/more/linux/networking/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provide networking management capabilities. 7 | ''' 8 | 9 | from provy.more.linux.networking.hosts import HostsRole 10 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = provy 4 | include = provy/* 5 | omit = provy/*/tests/* 6 | provy/tests/* 7 | provy/console.py 8 | test_*.* 9 | e2e.py 10 | 11 | [report] 12 | show_missing = True 13 | 14 | [html] 15 | title = provy coverage report 16 | -------------------------------------------------------------------------------- /provy/more/centos/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provide general utilities within CentOS distributions. 7 | ''' 8 | 9 | from provy.more.centos.utils.hostname import HostNameRole 10 | 11 | -------------------------------------------------------------------------------- /provy/more/debian/web/templates/local.settings.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from {{ settings_file }} import * 5 | 6 | {% for key, value in settings.iteritems() %} 7 | {{ key }} = {% if value is string %}"{{ value }}"{% else %}{{ value }}{% endif %} 8 | {% endfor %} 9 | -------------------------------------------------------------------------------- /provy/more/centos/networking/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provide networking management capabilities for centos distributions. 7 | ''' 8 | 9 | from provy.more.centos.networking.hosts import HostsRole 10 | -------------------------------------------------------------------------------- /provy/more/debian/networking/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provide networking management capabilities for debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.networking.hosts import HostsRole 10 | -------------------------------------------------------------------------------- /provy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | This is provy's main namespace. All built-in roles start from this namespace. 6 | ''' 7 | 8 | major_version = '0.7' 9 | release = '0-dev' 10 | 11 | __version__ = '%s.%s' % (major_version, release) 12 | version = __version__ 13 | -------------------------------------------------------------------------------- /provy/core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | provy's core classes. The modules in this namespace are the ones that run provy. 7 | ''' 8 | 9 | from provy.core.roles import Role 10 | from provy.core.runner import run 11 | from provy.core.utils import AskFor 12 | -------------------------------------------------------------------------------- /provy/more/base/database/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable database management for database servers as MySQL, MongoDB, Redis and such. 7 | ''' 8 | 9 | from provy.more.base.database.postgresql import BasePostgreSQLRole 10 | -------------------------------------------------------------------------------- /provy/more/debian/users/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable user management in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.users.user import UserRole 10 | from provy.more.debian.users.ssh import SSHRole 11 | -------------------------------------------------------------------------------- /provy/more/debian/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provision monitoring systems like `Supervisor `_ or log watch. 7 | ''' 8 | 9 | from provy.more.debian.monitoring.supervisor import SupervisorRole 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | before_install: 5 | - sudo apt-get update -qq 6 | - sudo apt-get install -qq python-dev swig 7 | # command to install dependencies 8 | install: pip install -r REQUIREMENTS --use-mirrors 9 | # command to run tests 10 | script: make build 11 | after_success: 12 | - coveralls 13 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.base.rst: -------------------------------------------------------------------------------- 1 | base Package 2 | ============ 3 | 4 | :mod:`base` Package 5 | ------------------- 6 | 7 | .. automodule:: provy.more.base 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Subpackages 13 | ----------- 14 | 15 | .. toctree:: 16 | 17 | provy.more.base.database 18 | 19 | -------------------------------------------------------------------------------- /provy/more/centos/messaging/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable message queueing service management as RabbitMq, Apache Qpid and such, in CentOS distributions. 7 | ''' 8 | 9 | from provy.more.centos.messaging.rabbitmq import RabbitMqRole 10 | 11 | -------------------------------------------------------------------------------- /provy/more/debian/messaging/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable message queueing service management as RabbitMq, Apache Qpid and such, in CentOS distributions. 7 | ''' 8 | 9 | from provy.more.debian.messaging.rabbitmq import RabbitMqRole 10 | 11 | -------------------------------------------------------------------------------- /provy/more/centos/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace relate to `Version Control Systems `_ support in Debian distributions, such as git, svn or mercurial. 7 | ''' 8 | 9 | from provy.more.centos.vcs.git import GitRole 10 | -------------------------------------------------------------------------------- /provy/more/debian/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace relate to `Version Control Systems `_ support in Debian distributions, such as git, svn or mercurial. 7 | ''' 8 | 9 | from provy.more.debian.vcs.git import GitRole 10 | -------------------------------------------------------------------------------- /tests/end_to_end/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant::Config.run do |config| 2 | config.vm.define :end_to_end do |inner_config| 3 | inner_config.vm.box = "precise64" 4 | inner_config.vm.box_url = 'http://files.vagrantup.com/precise64.box' 5 | inner_config.vm.forward_port(80, 8080) 6 | inner_config.vm.network(:hostonly, "33.33.33.33") 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /provy/more/centos/package/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable users to install packages using package managers such as Yum or Pip in CentOS distributions. 7 | ''' 8 | 9 | from provy.more.centos.package.yum import YumRole, PackageNotFound 10 | from provy.more.centos.package.pip import PipRole 11 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.rst: -------------------------------------------------------------------------------- 1 | more Package 2 | ============ 3 | 4 | :mod:`more` Package 5 | ------------------- 6 | 7 | .. automodule:: provy.more 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Subpackages 13 | ----------- 14 | 15 | .. toctree:: 16 | 17 | provy.more.base 18 | provy.more.centos 19 | provy.more.debian 20 | provy.more.linux 21 | 22 | -------------------------------------------------------------------------------- /tests/unit/fixtures/test_public_key: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1xEY9wbWaj5XCHyjvSPXptIQKGQujNIUxmCNWYSX+lB60aQVZKzUgIQ91DLnTlcW0cLUVS5jHsC3rxojZkOGtu430gYswba1OgkC5Jl7neFSi8eI1Al0IO8y/U496yLnf6aMQQUkFlysn4SnWIT3QICM0Ch/Iz2Cq68/wIWlUDs04rm8nyXodHri44NcZ4k9FDH/9ZNy857pkDCslQDXFT+qPMYmInpDoYy+69D3bhH95uUzdgsfMFi7+7iIUHwJfLH4jpk3vkLi6ara5gTQm8KUX2+UVIKqn099Q8Rb4v7cJiYADOQh9zPk2XHgNRwEAQarS0Z0JI9XQYZajNHrX -------------------------------------------------------------------------------- /provy/more/centos/database/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable database management for database servers as MySQL, MongoDB, Redis and such, in CentOS distributions. 7 | ''' 8 | 9 | from provy.more.centos.database.mysql import MySQLRole 10 | from provy.more.centos.database.postgresql import PostgreSQLRole 11 | -------------------------------------------------------------------------------- /provy/more/debian/programming/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to configure programming languages in Debian distributions. 7 | ''' 8 | from provy.more.debian.programming.nodejs import NodeJsRole 9 | from provy.more.debian.programming.ruby import RubyRole 10 | from provy.more.debian.programming.php import PHPRole 11 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.vcs.rst: -------------------------------------------------------------------------------- 1 | vcs Package 2 | =========== 3 | 4 | :mod:`vcs` Package 5 | ------------------ 6 | 7 | .. automodule:: provy.more.centos.vcs 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`git` Module 13 | ----------------- 14 | 15 | .. automodule:: provy.more.centos.vcs.git 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.vcs.rst: -------------------------------------------------------------------------------- 1 | vcs Package 2 | =========== 3 | 4 | :mod:`vcs` Package 5 | ------------------ 6 | 7 | .. automodule:: provy.more.debian.vcs 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`git` Module 13 | ----------------- 14 | 15 | .. automodule:: provy.more.debian.vcs.git 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /tests/end_to_end/provy-e2e-key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD6rB0e7O0kOIfQmChe2vDEybI3jjMTM4riJ0C7AwS1y7vEk6BTSoLuZv3XcLyJK+Ecs49ar8BOzRw5JDdCHRosKTRMFRPxGJt0yJwUQtzzCQ4Jy7w6ZEiNbVU7nujyzzztQbZN0XJL36F2yhBi4himhJfz2uTrvHhcOZYK7J7azOj4d/rKLGVJnvZPNfkWCwEiaUhyu18Zley4vi3glHXVynLNx+d6O7gJvFA0Zvp2qcaF5NHiYMnZfQ5rhWc2dhof8zk1m//t9bUlTgXCrUljmD/fBJ7vpMsL2W31BWz8gcRXaV3Im/cd+ZmseuyzSS6CsbamQYzsp2e24dmUNT0P provy@provy-e2e 2 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.users.rst: -------------------------------------------------------------------------------- 1 | users Package 2 | ============= 3 | 4 | :mod:`users` Package 5 | -------------------- 6 | 7 | .. automodule:: provy.more.centos.users 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`user` Module 13 | ------------------ 14 | 15 | .. automodule:: provy.more.centos.users.user 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /provy/more/debian/cache/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to provision caching engines, such as `Varnish `_ or `Memcached `_ in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.cache.varnish import VarnishRole 10 | from provy.more.debian.cache.memcached import MemcachedRole 11 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.utils.rst: -------------------------------------------------------------------------------- 1 | utils Package 2 | ============= 3 | 4 | :mod:`utils` Package 5 | -------------------- 6 | 7 | .. automodule:: provy.more.centos.utils 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`hostname` Module 13 | ---------------------- 14 | 15 | .. automodule:: provy.more.centos.utils.hostname 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.base.database.rst: -------------------------------------------------------------------------------- 1 | database Package 2 | ================ 3 | 4 | :mod:`database` Package 5 | ----------------------- 6 | 7 | .. automodule:: provy.more.base.database 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`postgresql` Module 13 | ------------------------ 14 | 15 | .. automodule:: provy.more.base.database.postgresql 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.linux.networking.rst: -------------------------------------------------------------------------------- 1 | networking Package 2 | ================== 3 | 4 | :mod:`networking` Package 5 | ------------------------- 6 | 7 | .. automodule:: provy.more.linux.networking 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`hosts` Module 13 | ------------------- 14 | 15 | .. automodule:: provy.more.linux.networking.hosts 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /provy/more/debian/security/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to configure security features in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.security.iptables import IPTablesRole 10 | from provy.more.debian.security.ufw import UFWRole 11 | from provy.more.debian.security.apparmor import AppArmorRole 12 | from provy.more.debian.security.selinux import SELinuxRole 13 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.messaging.rst: -------------------------------------------------------------------------------- 1 | messaging Package 2 | ================= 3 | 4 | :mod:`messaging` Package 5 | ------------------------ 6 | 7 | .. automodule:: provy.more.centos.messaging 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`rabbitmq` Module 13 | ---------------------- 14 | 15 | .. automodule:: provy.more.centos.messaging.rabbitmq 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.networking.rst: -------------------------------------------------------------------------------- 1 | networking Package 2 | ================== 3 | 4 | :mod:`networking` Package 5 | ------------------------- 6 | 7 | .. automodule:: provy.more.centos.networking 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`hosts` Module 13 | ------------------- 14 | 15 | .. automodule:: provy.more.centos.networking.hosts 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.networking.rst: -------------------------------------------------------------------------------- 1 | networking Package 2 | ================== 3 | 4 | :mod:`networking` Package 5 | ------------------------- 6 | 7 | .. automodule:: provy.more.debian.networking 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`hosts` Module 13 | ------------------- 14 | 15 | .. automodule:: provy.more.debian.networking.hosts 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.monitoring.rst: -------------------------------------------------------------------------------- 1 | monitoring Package 2 | ================== 3 | 4 | :mod:`monitoring` Package 5 | ------------------------- 6 | 7 | .. automodule:: provy.more.debian.monitoring 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`supervisor` Module 13 | ------------------------ 14 | 15 | .. automodule:: provy.more.debian.monitoring.supervisor 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /provy/more/centos/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are suited for provisioning centos servers. 7 | ''' 8 | 9 | from provy.more.centos.package import * 10 | from provy.more.centos.users import * 11 | from provy.more.centos.networking import * 12 | from provy.more.centos.vcs import * 13 | from provy.more.centos.messaging import * 14 | from provy.more.centos.utils import * 15 | from provy.more.centos.database import * 16 | -------------------------------------------------------------------------------- /tests/end_to_end/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from unittest import TestCase 5 | 6 | from nose.tools import istest 7 | import requests 8 | 9 | 10 | class EndToEndTests(TestCase): 11 | @istest 12 | def gets_response_from_tornado(self): 13 | response = requests.get('http://{}:{}'.format(os.environ['PROVY_HOST'], os.environ['PROVY_PORT'])) 14 | 15 | self.assertEqual(response.status_code, 200) 16 | self.assertEqual(response.content, 'Hello, world') 17 | -------------------------------------------------------------------------------- /docs/source/api/provy.rst: -------------------------------------------------------------------------------- 1 | provy Package 2 | ============= 3 | 4 | :mod:`provy` Package 5 | -------------------- 6 | 7 | .. automodule:: provy.__init__ 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`console` Module 13 | --------------------- 14 | 15 | .. automodule:: provy.console 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | Subpackages 21 | ----------- 22 | 23 | .. toctree:: 24 | 25 | provy.core 26 | provy.more 27 | 28 | -------------------------------------------------------------------------------- /provy/more/debian/database/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable database management for database servers as MySQL, MongoDB, Redis and such, in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.database.mongodb import MongoDBRole 10 | from provy.more.debian.database.mysql import MySQLRole 11 | from provy.more.debian.database.postgresql import PostgreSQLRole 12 | from provy.more.debian.database.redis import RedisRole 13 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.rst: -------------------------------------------------------------------------------- 1 | centos Package 2 | ============== 3 | 4 | :mod:`centos` Package 5 | --------------------- 6 | 7 | .. automodule:: provy.more.centos 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Subpackages 13 | ----------- 14 | 15 | .. toctree:: 16 | 17 | provy.more.centos.database 18 | provy.more.centos.messaging 19 | provy.more.centos.networking 20 | provy.more.centos.package 21 | provy.more.centos.users 22 | provy.more.centos.utils 23 | provy.more.centos.vcs 24 | 25 | -------------------------------------------------------------------------------- /provy/more/debian/web/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to configure either Web Servers (apache, nginx) or Web App Servers (tornado, django, rails) in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.web.apache import ApacheRole 10 | from provy.more.debian.web.nginx import NginxRole 11 | from provy.more.debian.web.tornado import TornadoRole 12 | from provy.more.debian.web.django import DjangoRole 13 | from provy.more.debian.web.rails import RailsRole 14 | -------------------------------------------------------------------------------- /docs/source/recipes.rst: -------------------------------------------------------------------------------- 1 | provy Recipes 2 | ============= 3 | 4 | Django + Nginx same server 5 | -------------------------- 6 | 7 | .. image:: images/django-nginx-recipe.png 8 | 9 | This recipe features a django website with 4 processes and 2 threads per process. 10 | 11 | Nginx serves the requests via reverse proxy, while load balancing the 4 processes. 12 | 13 | Each django process is a gunicorn process bound to a port ranging from 8000-8003. 14 | 15 | Django's static files are served using nginx. 16 | 17 | To read more about this recipe, :doc:`click here `. -------------------------------------------------------------------------------- /tests/end_to_end/files/website.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import tornado.ioloop 6 | import tornado.web 7 | 8 | 9 | class MainHandler(tornado.web.RequestHandler): 10 | def get(self): 11 | self.write("Hello, world") 12 | 13 | 14 | application = tornado.web.Application([ 15 | (r"/", MainHandler), 16 | ]) 17 | 18 | 19 | if __name__ == "__main__": 20 | port = int(sys.argv[1]) 21 | application.listen(port, '0.0.0.0') 22 | print ">> Website running at http://0.0.0.0:%d" % port 23 | tornado.ioloop.IOLoop.instance().start() 24 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.users.rst: -------------------------------------------------------------------------------- 1 | users Package 2 | ============= 3 | 4 | :mod:`users` Package 5 | -------------------- 6 | 7 | .. automodule:: provy.more.debian.users 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`ssh` Module 13 | ----------------- 14 | 15 | .. automodule:: provy.more.debian.users.ssh 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`user` Module 21 | ------------------ 22 | 23 | .. automodule:: provy.more.debian.users.user 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | -------------------------------------------------------------------------------- /provy/more/debian/web/templates/rails-nginx.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name {{ host }}; 4 | root {{ path }}/public; 5 | passenger_enabled on; 6 | 7 | location ~ ^/assets/ { 8 | expires 1y; 9 | add_header Cache-Control public; 10 | 11 | # Some browsers still send conditional-GET requests if there's a 12 | # Last-Modified header or an ETag header even if they haven't 13 | # reached the expiry date sent in the Expires header. 14 | add_header Last-Modified ""; 15 | add_header ETag ""; 16 | break; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.package.rst: -------------------------------------------------------------------------------- 1 | package Package 2 | =============== 3 | 4 | :mod:`package` Package 5 | ---------------------- 6 | 7 | .. automodule:: provy.more.centos.package 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`pip` Module 13 | ----------------- 14 | 15 | .. automodule:: provy.more.centos.package.pip 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`yum` Module 21 | ----------------- 22 | 23 | .. automodule:: provy.more.centos.package.yum 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.cache.rst: -------------------------------------------------------------------------------- 1 | cache Package 2 | ============= 3 | 4 | :mod:`cache` Package 5 | -------------------- 6 | 7 | .. automodule:: provy.more.debian.cache 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`memcached` Module 13 | ----------------------- 14 | 15 | .. automodule:: provy.more.debian.cache.memcached 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`varnish` Module 21 | --------------------- 22 | 23 | .. automodule:: provy.more.debian.cache.varnish 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | -------------------------------------------------------------------------------- /tests/end_to_end/files/website: -------------------------------------------------------------------------------- 1 | upstream frontends { 2 | server {{ host }}:8000; 3 | server {{ host }}:8001; 4 | server {{ host }}:8002; 5 | server {{ host }}:8003; 6 | } 7 | 8 | server { 9 | listen {{ port }}; 10 | server_name localhost {{ host }}; 11 | 12 | access_log /home/frontend/website.access.log; 13 | 14 | location / { 15 | proxy_pass_header Server; 16 | proxy_set_header Host $http_host; 17 | proxy_redirect off; 18 | proxy_set_header X-Real-IP $remote_addr; 19 | proxy_set_header X-Scheme $scheme; 20 | proxy_pass http://frontends; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/source/roles-docs.rst: -------------------------------------------------------------------------------- 1 | Roles documentation 2 | =================== 3 | 4 | *provy* is basically divided in two namespaces: :doc:`provy.core ` and :doc:`provy.more `. It is divided like this to make it easier to find the roles you need. 5 | 6 | :doc:`provy.core ` features the code that actually makes *provy* run, like the console app or the base Role. 7 | 8 | :doc:`provy.more ` features every single specialized role that helps users in provisioning servers, like *NginxRole* or *AptitudeRole*. 9 | 10 | If you're looking for all the modules tree, :doc:`click here ` -------------------------------------------------------------------------------- /docs/source/what-are-roles.rst: -------------------------------------------------------------------------------- 1 | What are roles? 2 | =============== 3 | 4 | Roles are the most important concept in *provy*. A role specifies one server capability, like user management or a package provider. 5 | 6 | *provy* comes with many bundled roles, but you can very easily create your own roles. As a matter of fact, if you followed the :doc:`getting-started` tutorial, you have already created two custom roles. 7 | 8 | Creating new roles is as easy as creating a class that inherits from *provy.Role* and implements a *provision* method. 9 | 10 | This method is the one *provy* will call when this role is being provisioned into a given server. -------------------------------------------------------------------------------- /provy/more/debian/package/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are meant to enable users to install packages using package managers such as Aptitude or Pip in Debian distributions. 7 | ''' 8 | 9 | from provy.more.debian.package.aptitude import AptitudeRole, PackageNotFound 10 | from provy.more.debian.package.gem import GemRole 11 | from provy.more.debian.package.pip import PipRole 12 | from provy.more.debian.package.virtualenv import VirtualenvRole 13 | #from provy.more.debian.package.npm import NPMRole # imported at provy.more.debian.__init__ to avoid circular imports 14 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.centos.database.rst: -------------------------------------------------------------------------------- 1 | database Package 2 | ================ 3 | 4 | :mod:`database` Package 5 | ----------------------- 6 | 7 | .. automodule:: provy.more.centos.database 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`mysql` Module 13 | ------------------- 14 | 15 | .. automodule:: provy.more.centos.database.mysql 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`postgresql` Module 21 | ------------------------ 22 | 23 | .. automodule:: provy.more.centos.database.postgresql 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.rst: -------------------------------------------------------------------------------- 1 | debian Package 2 | ============== 3 | 4 | :mod:`debian` Package 5 | --------------------- 6 | 7 | .. automodule:: provy.more.debian 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Subpackages 13 | ----------- 14 | 15 | .. toctree:: 16 | 17 | provy.more.debian.cache 18 | provy.more.debian.database 19 | provy.more.debian.monitoring 20 | provy.more.debian.networking 21 | provy.more.debian.package 22 | provy.more.debian.programming 23 | provy.more.debian.security 24 | provy.more.debian.users 25 | provy.more.debian.vcs 26 | provy.more.debian.web 27 | 28 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant::Config.run do |config| 2 | config.vm.define :frontend do |inner_config| 3 | inner_config.vm.box = "precise64" 4 | inner_config.vm.box_url = 'http://files.vagrantup.com/precise64.box' 5 | inner_config.vm.forward_port("http", 80, 8080) 6 | inner_config.vm.network("33.33.33.33") 7 | end 8 | 9 | config.vm.define :backend do |inner_config| 10 | inner_config.vm.box = "precise64" 11 | inner_config.vm.box_url = 'http://files.vagrantup.com/precise64.box' 12 | inner_config.vm.forward_port("http", 80, 8081) 13 | inner_config.vm.network("33.33.33.34") 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /tests/unit/more/linux/networking/test_hosts.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.linux.networking.hosts import HostsRole 4 | from tests.unit.tools.helpers import ProvyTestCase 5 | 6 | 7 | class HostsRoleTest(ProvyTestCase): 8 | def setUp(self): 9 | super(HostsRoleTest, self).setUp() 10 | self.role = HostsRole(prov=None, context={}) 11 | 12 | @istest 13 | def ensures_a_host_line_exists_in_the_hosts_file(self): 14 | with self.mock_role_method('ensure_line') as ensure_line: 15 | self.role.ensure_host('my-server', '0.0.0.0') 16 | 17 | ensure_line.assert_called_once_with('0.0.0.0 my-server', '/etc/hosts', sudo=True) 18 | -------------------------------------------------------------------------------- /tests/unit/more/debian/database/test_redis.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import AptitudeRole, RedisRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class RedisRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(RedisRoleTest, self).setUp() 11 | self.role = RedisRole(prov=None, context={}) 12 | 13 | @istest 14 | def installs_necessary_packages_to_provision(self): 15 | with self.using_stub(AptitudeRole) as mock_aptitude: 16 | self.role.provision() 17 | install_calls = mock_aptitude.ensure_package_installed.mock_calls 18 | self.assertEqual(install_calls, [call('redis-server'), call('python-redis')]) 19 | -------------------------------------------------------------------------------- /provy/more/debian/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # flake8: noqa 4 | 5 | ''' 6 | Roles in this namespace are suited for provisioning debian-based distributions (Debian, Ubuntu, etc.). 7 | ''' 8 | 9 | from provy.more.debian.package import * 10 | from provy.more.debian.users import * 11 | from provy.more.debian.cache import * 12 | from provy.more.debian.database import * 13 | from provy.more.debian.monitoring import * 14 | from provy.more.debian.vcs import * 15 | from provy.more.debian.web import * 16 | from provy.more.debian.networking import * 17 | from provy.more.debian.programming import * 18 | from provy.more.debian.security import * 19 | from provy.more.debian.package.npm import NPMRole # imported here to avoid circular imports 20 | from provy.more.debian.messaging import * 21 | -------------------------------------------------------------------------------- /provy/more/debian/monitoring/templates/supervisord.init.template: -------------------------------------------------------------------------------- 1 | # Supervisord auto-start 2 | # 3 | # description: Auto-starts supervisord 4 | # processname: supervisord 5 | # pidfile: /var/run/supervisord.pid 6 | 7 | SUPERVISORD=/usr/local/bin/supervisord 8 | SUPERVISORCTL=/usr/local/bin/supervisorctl 9 | 10 | case $1 in 11 | start) 12 | echo -n "Starting supervisord: " 13 | $SUPERVISORD -c {{ config_file }} 14 | echo 15 | ;; 16 | stop) 17 | echo -n "Stopping supervisord: " 18 | $SUPERVISORCTL -c {{ config_file }} shutdown 19 | echo 20 | ;; 21 | restart) 22 | echo -n "Restarting supervisord: " 23 | $SUPERVISORCTL -c {{ config_file }} update 24 | $SUPERVISORCTL -c {{ config_file }} restart all 25 | echo 26 | ;; 27 | esac 28 | -------------------------------------------------------------------------------- /tests/unit/more/debian/web/test_tornado.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.debian import TornadoRole, AptitudeRole, PipRole 4 | from tests.unit.tools.helpers import ProvyTestCase 5 | 6 | 7 | class TornadoRoleTest(ProvyTestCase): 8 | def setUp(self): 9 | super(TornadoRoleTest, self).setUp() 10 | self.role = TornadoRole(prov=None, context={}) 11 | 12 | @istest 13 | def installs_necessary_packages_to_provision(self): 14 | with self.using_stub(AptitudeRole) as aptitude, self.using_stub(PipRole) as pip: 15 | self.role.provision() 16 | 17 | aptitude.ensure_up_to_date.assert_called_once_with() 18 | aptitude.ensure_package_installed.assert_called_once_with('python-pycurl') 19 | pip.ensure_package_installed.assert_called_once_with('tornado') 20 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.programming.rst: -------------------------------------------------------------------------------- 1 | programming Package 2 | =================== 3 | 4 | :mod:`programming` Package 5 | -------------------------- 6 | 7 | .. automodule:: provy.more.debian.programming 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`nodejs` Module 13 | -------------------- 14 | 15 | .. automodule:: provy.more.debian.programming.nodejs 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`php` Module 21 | ----------------- 22 | 23 | .. automodule:: provy.more.debian.programming.php 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`ruby` Module 29 | ------------------ 30 | 31 | .. automodule:: provy.more.debian.programming.ruby 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/source/themes/provy/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | 3 | inherit = default 4 | stylesheet = provy.css 5 | pygments_style = sphinx 6 | 7 | 8 | 9 | [options] 10 | 11 | textcolor = rgb(78, 78, 78) 12 | linkcolor = rgb(214, 84, 0) 13 | visitedlinkcolor = rgb(214, 84, 0) 14 | 15 | sidebarbgcolor = rgb(242, 242, 242) 16 | sidebartextcolor = rgb(78, 78, 78) 17 | sidebarlinkcolor = rgb(214, 84, 0) 18 | 19 | headbgcolor = rgb(250, 125, 43) 20 | headtextcolor = rgb(255, 255, 255) 21 | headlinkcolor = rgb(214, 84, 0) 22 | 23 | relbarbgcolor = rgb(242, 242, 242) 24 | relbartextcolor = rgb(78, 78, 78) 25 | relbarlinkcolor = rgb(214, 84, 0) 26 | 27 | bodyfont = 'Maven Pro', Arial, cursive 28 | headfont = 'Lobster', Arial, cursive 29 | 30 | codebgcolor = rgb(242, 242, 242) 31 | 32 | footerbgcolor = rgb(242, 242, 242) 33 | footertextcolor = rgb(78, 78, 78) 34 | -------------------------------------------------------------------------------- /provy/more/centos/networking/hosts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide hosts management operations for centos distributions. 6 | ''' 7 | 8 | from provy.more.linux.networking.hosts import HostsRole as Hosts 9 | 10 | 11 | class HostsRole(Hosts): 12 | ''' 13 | This role provides hosts file management utilities for centos distributions. 14 | 15 | This is just a class wrapper over :class:`provy.more.linux.networking.hosts.HostsRole` 16 | 17 | Example: 18 | :: 19 | 20 | from provy.core import Role 21 | from provy.more.centos import HostsRole 22 | 23 | class MySampleRole(Role): 24 | def provision(self): 25 | with self.using(HostsRole) as role: 26 | role.ensure_host('localhost', '127.0.0.1') 27 | ''' 28 | -------------------------------------------------------------------------------- /provy/more/debian/networking/hosts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide hosts management operations for debian distributions. 6 | ''' 7 | 8 | from provy.more.linux.networking.hosts import HostsRole as Hosts 9 | 10 | 11 | class HostsRole(Hosts): 12 | ''' 13 | This role provides hosts file management utilities for debian distributions. 14 | 15 | This is just a class wrapper over :class:`provy.more.linux.networking.hosts.HostsRole` 16 | 17 | Example: 18 | :: 19 | 20 | from provy.core import Role 21 | from provy.more.debian import HostsRole 22 | 23 | class MySampleRole(Role): 24 | def provision(self): 25 | with self.using(HostsRole) as role: 26 | role.ensure_host('localhost', '127.0.0.1') 27 | ''' 28 | -------------------------------------------------------------------------------- /docs/source/api/provy.core.rst: -------------------------------------------------------------------------------- 1 | core Package 2 | ============ 3 | 4 | :mod:`core` Package 5 | ------------------- 6 | 7 | .. automodule:: provy.core 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`errors` Module 13 | -------------------- 14 | 15 | .. automodule:: provy.core.errors 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`roles` Module 21 | ------------------- 22 | 23 | .. automodule:: provy.core.roles 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`runner` Module 29 | -------------------- 30 | 31 | .. automodule:: provy.core.runner 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`utils` Module 37 | ------------------- 38 | 39 | .. automodule:: provy.core.utils 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | -------------------------------------------------------------------------------- /tests/end_to_end/files/nginx.conf: -------------------------------------------------------------------------------- 1 | user {{ user }}; 2 | worker_processes 1; 3 | 4 | error_log /home/frontend/error.log; 5 | pid /home/frontend/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | use epoll; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | access_log /home/frontend/nginx.access.log; 17 | 18 | keepalive_timeout 65; 19 | proxy_read_timeout 200; 20 | sendfile on; 21 | tcp_nopush on; 22 | tcp_nodelay on; 23 | gzip on; 24 | gzip_min_length 1000; 25 | gzip_proxied any; 26 | gzip_types text/plain text/css text/xml 27 | application/x-javascript application/xml 28 | application/atom+xml text/javascript; 29 | 30 | proxy_next_upstream error; 31 | 32 | include /etc/nginx/conf.d/*.conf; 33 | include /etc/nginx/sites-enabled/*; 34 | } 35 | -------------------------------------------------------------------------------- /docs/source/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | These are the changes in the project, from the latest version on top to the earlier ones. 5 | 6 | 0.6.1 7 | ----- 8 | 9 | * Documentation and website completely revamped! 10 | 11 | * Replaced M2Crypto with PyCrypto 12 | 13 | * New methods: 14 | 15 | - :meth:`provy.core.roles.Role.execute_python_script` 16 | 17 | - :meth:`provy.core.roles.Role.remote_list_directory` 18 | 19 | - :meth:`provy.core.roles.Role.create_remote_temp_file` 20 | 21 | - :meth:`provy.core.roles.Role.create_remote_temp_dir` 22 | 23 | - :meth:`provy.more.debian.package.virtualenv.VirtualenvRole.get_base_directory` 24 | 25 | * Changed from personal project to community-managed project in GitHub 26 | 27 | * Small changes in the API (we tried to change the least that we could, and reduce impact as much as possible) 28 | 29 | * Code coverage raised to 75%, as measured at the time of the release 30 | 31 | * Multiple bugfixes 32 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.security.rst: -------------------------------------------------------------------------------- 1 | security Package 2 | ================ 3 | 4 | :mod:`security` Package 5 | ----------------------- 6 | 7 | .. automodule:: provy.more.debian.security 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`apparmor` Module 13 | ---------------------- 14 | 15 | .. automodule:: provy.more.debian.security.apparmor 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`iptables` Module 21 | ---------------------- 22 | 23 | .. automodule:: provy.more.debian.security.iptables 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`selinux` Module 29 | --------------------- 30 | 31 | .. automodule:: provy.more.debian.security.selinux 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`ufw` Module 37 | ----------------- 38 | 39 | .. automodule:: provy.more.debian.security.ufw 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.database.rst: -------------------------------------------------------------------------------- 1 | database Package 2 | ================ 3 | 4 | :mod:`database` Package 5 | ----------------------- 6 | 7 | .. automodule:: provy.more.debian.database 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`mongodb` Module 13 | --------------------- 14 | 15 | .. automodule:: provy.more.debian.database.mongodb 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`mysql` Module 21 | ------------------- 22 | 23 | .. automodule:: provy.more.debian.database.mysql 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`postgresql` Module 29 | ------------------------ 30 | 31 | .. automodule:: provy.more.debian.database.postgresql 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`redis` Module 37 | ------------------- 38 | 39 | .. automodule:: provy.more.debian.database.redis 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | -------------------------------------------------------------------------------- /docs/source/ideas.rst: -------------------------------------------------------------------------------- 1 | Ideas, features or suggestions 2 | ============================== 3 | 4 | If you have any ideas that can improve *provy*, or feature requests or even just some suggestion on the library or on this website, please let us know! We value your opinion more than anything else. If you don't have the time to contribute to the project, contribute with your ideas. 5 | 6 | We have setup an easy-to-use way of providing feedback with the help of the nice people at `uservoice.com `_. 7 | 8 | You can go to our `uservoice.com `_ page at http://provy.uservoice.com and vote in the ideas given by users, thus helping us select ideas that will be implemented first. 9 | 10 | Or you can just click on the button in the right-bottom of the screen that says *feedback & support* and tell us any number of things you think will help the project. 11 | 12 | In the event that you not only want to send us your idea, but want to implement it and contribute to the project, keep reading the Contributing section. -------------------------------------------------------------------------------- /provy/more/debian/web/templates/rails.nginx.conf.template: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | passenger_root /usr/local/lib/ruby/gems/1.9.1/gems/passenger-3.0.9; 13 | passenger_ruby /usr/local/bin/ruby; 14 | 15 | include mime.types; 16 | default_type application/octet-stream; 17 | 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | access_log /var/log/nginx/access.log main; 23 | 24 | sendfile on; 25 | #tcp_nopush on; 26 | 27 | #keepalive_timeout 0; 28 | keepalive_timeout 65; 29 | 30 | gzip on; 31 | gzip_disable "MSIE [1-6]\.(?!.*SV1)"; 32 | 33 | include /etc/nginx/conf.d/*.conf; 34 | include /etc/nginx/sites-enabled/*; 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /docs/source/using-roles.rst: -------------------------------------------------------------------------------- 1 | Using Roles in my Roles 2 | ======================= 3 | 4 | As you may have noticed, *provy* provides a special syntax for using other roles in your role. Say we need to use the *AptitudeRole* in our role. This is how we'd do it:: 5 | 6 | class MyRole(Role): 7 | def provision(self): 8 | with self.using(AptitudeRole) as role: 9 | # do something with role 10 | role.ensure_package_installed('some-package') 11 | 12 | The *using* method of the Role class is a special way of using other roles. The reason for using it is that it maintains context and the *provy* lifecycle (more on both later). 13 | 14 | If you just want to provision another role in your role, you can use:: 15 | 16 | class MyRole(Role): 17 | def provision(self): 18 | self.provision_role(TornadoRole) 19 | 20 | The *provision_role* method does exactly the same as the *using* method, except it does not enter a with block. This should be used when you don't wnat to call anything in the role, instead just have it provisioned. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef PROVY_COVER 2 | PROVY_COVER=provy 3 | COVER_PERCENTAGE=100 4 | else 5 | COVER_PERCENTAGE=0 6 | endif 7 | 8 | 9 | setup: 10 | @pip install --requirement=REQUIREMENTS 11 | 12 | docs: 13 | @python docs.py 14 | 15 | test: 16 | @env PYTHONHASHSEED=random PYTHONPATH=. nosetests --with-coverage --cover-min-percentage=$(COVER_PERCENTAGE) --cover-package=$(PROVY_COVER) --cover-erase --cover-html --cover-xml --with-yanc --with-xtraceback -e end_to_end tests/ 17 | 18 | build: test 19 | @echo Running syntax check... 20 | @flake8 . --ignore=E501 21 | 22 | # This target is for hardcore linting only, not taken into consideration for the build. 23 | lint: 24 | @echo Starting hardcore lint... 25 | @pylint --rcfile=pylint.cfg provy 26 | 27 | sysinfo: 28 | @echo Free disk space: 29 | @df -h 30 | @echo Plaform info: 31 | @uname -a 32 | @echo Distribution info: 33 | @lsb_release -a 34 | @echo 'Memory info (in megabytes):' 35 | @free -m 36 | 37 | 38 | 39 | # Targets for the end-to-end test. Requires Vagrant to be installed. 40 | 41 | end-to-end: 42 | @make -C tests/end_to_end end-to-end 43 | -------------------------------------------------------------------------------- /provy/more/linux/networking/hosts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide hosts management operations. 6 | ''' 7 | 8 | from provy.core import Role 9 | 10 | 11 | class HostsRole(Role): 12 | ''' 13 | This role provides hosts file management utilities. 14 | 15 | Example: 16 | :: 17 | 18 | from provy.core import Role 19 | from provy.more.linux import HostsRole 20 | 21 | class MySampleRole(Role): 22 | def provision(self): 23 | with self.using(HostsRole) as role: 24 | role.ensure_host('localhost', '127.0.0.1') 25 | ''' 26 | 27 | def ensure_host(self, host_name, ip): 28 | ''' 29 | Makes sure that a certain host is configured in the hosts file. 30 | 31 | :param host_name: The hostname. 32 | :type host_name: :class:`str` 33 | :param ip: The IP to which the :data:`host_name` will point to. 34 | :type ip: :class:`str` 35 | ''' 36 | self.ensure_line('%s %s' % (ip, host_name), '/etc/hosts', sudo=True) 37 | -------------------------------------------------------------------------------- /tests/unit/more/debian/programming/test_ruby.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.debian import AptitudeRole, RubyRole 4 | from provy.more.debian.programming.ruby import UPDATE_ALTERNATIVES_COMMAND 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class RubyRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(RubyRoleTest, self).setUp() 11 | self.role = RubyRole(prov=None, context={}) 12 | 13 | @istest 14 | def installs_necessary_packages_to_provision(self): 15 | with self.using_stub(AptitudeRole) as aptitude, self.execute_mock() as execute: 16 | self.role.provision() 17 | 18 | update_alternatives_command = UPDATE_ALTERNATIVES_COMMAND.format( 19 | version=self.role.version, 20 | priority=self.role.priority, 21 | ) 22 | aptitude.ensure_up_to_date.assert_called_once_with() 23 | aptitude.ensure_package_installed.assert_called_once_with('ruby{version}-full'.format(version=self.role.version)) 24 | execute.assert_called_once_with(update_alternatives_command, sudo=True) 25 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.web.rst: -------------------------------------------------------------------------------- 1 | web Package 2 | =========== 3 | 4 | :mod:`web` Package 5 | ------------------ 6 | 7 | .. automodule:: provy.more.debian.web 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`apache` Module 13 | -------------------- 14 | 15 | .. automodule:: provy.more.debian.web.apache 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`django` Module 21 | -------------------- 22 | 23 | .. automodule:: provy.more.debian.web.django 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`nginx` Module 29 | ------------------- 30 | 31 | .. automodule:: provy.more.debian.web.nginx 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`rails` Module 37 | ------------------- 38 | 39 | .. automodule:: provy.more.debian.web.rails 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`tornado` Module 45 | --------------------- 46 | 47 | .. automodule:: provy.more.debian.web.tornado 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 Bernardo Heynemann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # WARNING 2 | 3 | This project is not maintained anymore. 4 | 5 | # provy 6 | 7 | provy is a provisioning service in python. 8 | 9 | ## Status of the project 10 | [![Build Status](https://secure.travis-ci.org/python-provy/provy.png)](http://travis-ci.org/python-provy/provy) 11 | [![Coverage Status](https://coveralls.io/repos/python-provy/provy/badge.png?branch=master)](https://coveralls.io/r/python-provy/provy?branch=master) 12 | 13 | ## Examples 14 | 15 | For an example script check the [test provyfile.py](https://github.com/python-provy/provy/blob/master/tests/functional/fixtures/provyfile.py). 16 | 17 | ## Quick start 18 | 19 | To run the provyfile script in the command-line you use:: 20 | 21 | provy -s prod # provisions all prod servers 22 | provy -s prod.frontends # provision all front-end servers in prod 23 | provy -s prod.frontends.server1 # provision only server1 24 | 25 | This command will provision all the webservers described under webservers (-s) with 26 | the webserver role (-r). 27 | 28 | ## Documentation 29 | 30 | For more documentation on how to use it, go to [the provy page](https://provy.readthedocs.org/) 31 | 32 | -------------------------------------------------------------------------------- /docs/source/api/provy.more.debian.package.rst: -------------------------------------------------------------------------------- 1 | package Package 2 | =============== 3 | 4 | :mod:`package` Package 5 | ---------------------- 6 | 7 | .. automodule:: provy.more.debian.package 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`aptitude` Module 13 | ---------------------- 14 | 15 | .. automodule:: provy.more.debian.package.aptitude 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`gem` Module 21 | ----------------- 22 | 23 | .. automodule:: provy.more.debian.package.gem 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`npm` Module 29 | ----------------- 30 | 31 | .. automodule:: provy.more.debian.package.npm 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`pip` Module 37 | ----------------- 38 | 39 | .. automodule:: provy.more.debian.package.pip 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`virtualenv` Module 45 | ------------------------ 46 | 47 | .. automodule:: provy.more.debian.package.virtualenv 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | -------------------------------------------------------------------------------- /docs/source/supported-os.rst: -------------------------------------------------------------------------------- 1 | Supported Operating Systems 2 | =========================== 3 | 4 | At this point, *provy* only supports Debian-based (`Debian `_, `Ubuntu `_ etc) and `CentOS `_ distros. If you think your distribution should be here, please consider :doc:`contributing `. 5 | 6 | This area details what features of *provy* are supported in each operating system. 7 | 8 | Debian distributions and Ubuntu 9 | ------------------------------- 10 | 11 | .. image:: images/debian-logo.png 12 | .. image:: images/ubuntu-logo.png 13 | 14 | Currently all features are supported under Debian-based distributions (including `Ubuntu `_). 15 | 16 | The easiest way to verify what's available is checking the :doc:`roles-docs` section. 17 | 18 | CentOS distributions 19 | -------------------- 20 | 21 | .. image:: images/centos-logo.png 22 | 23 | Currently support to user management, git repository management and packaging (via pip and yum) are supported. 24 | 25 | More supported features to come soon. If you think you can help improve this, please consider :doc:`contributing `. -------------------------------------------------------------------------------- /docs/source/running.rst: -------------------------------------------------------------------------------- 1 | Running provy 2 | ============= 3 | 4 | *provy* comes with a console runner. It's the same one we used in the :doc:`getting-started` tutorial. 5 | 6 | The console runner supports some options. For more information you can use the --help command. You should see output like the following:: 7 | 8 | $ provy --help 9 | Usage: console.py [options] 10 | 11 | Options: 12 | -h, --help show this help message and exit 13 | -s SERVER, --server=SERVER 14 | Servers to provision with the specified role. This is 15 | a recursive option. 16 | -p PASSWORD, --password=PASSWORD 17 | Password to use for authentication with servers. 18 | If passwords differ from server to server this does 19 | not work. 20 | 21 | The option you are most likely to use is the *server* option. It tells *provy* what servers you want provisioned. 22 | 23 | As we saw in the :doc:`provyfile` section, we can also supply *AskFor* arguments when running *provy*. 24 | 25 | All arguments must take the form of key=value, with no spaces. The key must be exactly the same as the one in the *AskFor* definition, case-sensitive. -------------------------------------------------------------------------------- /tests/unit/more/debian/database/test_postgresql.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import AptitudeRole, PostgreSQLRole 5 | from tests.unit.more.base.database import test_postgresql 6 | 7 | 8 | class PostgreSQLRoleTestCase(test_postgresql.PostgreSQLRoleTestCase): 9 | def setUp(self): 10 | super(PostgreSQLRoleTestCase, self).setUp() 11 | self.role = PostgreSQLRole(prov=None, context={}) 12 | 13 | 14 | class PostgreSQLRoleTest(PostgreSQLRoleTestCase): 15 | @istest 16 | def installs_necessary_packages_to_provision_to_ubuntu(self): 17 | with self.using_stub(AptitudeRole) as mock_aptitude, self.provisioning_to('ubuntu'): 18 | self.role.provision() 19 | install_calls = mock_aptitude.ensure_package_installed.mock_calls 20 | self.assertEqual(install_calls, [call('postgresql'), call('postgresql-server-dev-9.2')]) 21 | 22 | @istest 23 | def installs_necessary_packages_to_provision_to_debian(self): 24 | with self.using_stub(AptitudeRole) as mock_aptitude, self.provisioning_to('debian'): 25 | self.role.provision() 26 | install_calls = mock_aptitude.ensure_package_installed.mock_calls 27 | self.assertEqual(install_calls, [call('postgresql'), call('postgresql-server-dev-8.4')]) 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | from provy import __version__ 5 | 6 | setup( 7 | name='provy', 8 | version=__version__, 9 | description="provy is an easy-to-use server provisioning tool.", 10 | long_description="provy is an easy-to-use server provisioning tool.", 11 | keywords='provisioning devops infrastructure server', 12 | author='Bernardo Heynemann', 13 | author_email='heynemann@gmail.com', 14 | url='https://provy.readthedocs.org', 15 | license='MIT', 16 | classifiers=[ 17 | 'Development Status :: 3 - Alpha', 18 | 'Intended Audience :: Developers', 19 | 'License :: OSI Approved :: MIT License', 20 | 'Natural Language :: English', 21 | 'Operating System :: MacOS', 22 | 'Operating System :: Microsoft :: Windows', 23 | 'Operating System :: POSIX :: Linux', 24 | 'Programming Language :: Python :: 2.6', 25 | 'Topic :: System :: Installation/Setup' 26 | ], 27 | packages=find_packages(exclude=['tests']), 28 | include_package_data=True, 29 | package_data={ 30 | '': ['*.template'], 31 | }, 32 | 33 | install_requires=[ 34 | "fabric", 35 | "jinja2", 36 | "configobj", 37 | ], 38 | 39 | entry_points={ 40 | 'console_scripts': [ 41 | 'provy = provy.console:main', 42 | ], 43 | }, 44 | 45 | ) 46 | -------------------------------------------------------------------------------- /provy/more/debian/database/redis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `Redis `_ key-value store management utilities for Debian distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.debian import AptitudeRole 10 | 11 | 12 | class RedisRole(Role): 13 | ''' 14 | This role provides `Redis `_ key-value store management utilities for Debian distributions. 15 | 16 | Example: 17 | :: 18 | 19 | from provy.core import Role 20 | from provy.more.debian import RedisRole 21 | 22 | class MySampleRole(Role): 23 | def provision(self): 24 | self.provision_role(RedisRole) 25 | ''' 26 | 27 | def provision(self): 28 | ''' 29 | Installs `Redis `_ and its dependencies. 30 | This method should be called upon if overriden in base classes, or Redis won't work properly in the remote server. 31 | 32 | Example: 33 | :: 34 | 35 | class MySampleRole(Role): 36 | def provision(self): 37 | self.provision_role(RedisRole) # no need to call this if using with block. 38 | ''' 39 | with self.using(AptitudeRole) as aptitude: 40 | aptitude.ensure_package_installed('redis-server') 41 | aptitude.ensure_package_installed('python-redis') 42 | -------------------------------------------------------------------------------- /docs/source/whats-provy.rst: -------------------------------------------------------------------------------- 1 | What's provy and provisioning 2 | ============================= 3 | 4 | According to `Wikipedia `_, provisioning is "the process of preparing and equipping a network to allow it to provide (new) services to its users". 5 | 6 | We'll draw from this concept the part of preparing and equipping. 7 | 8 | **provy** is a infrastructure provisioning automation tool. Its main goal is making it easy to create highly-scalable architectures with simple scripts. 9 | 10 | **provy** stands on the shoulder of giants! `fabric `_ for the networking part and `jinja2 `_ for templating capabilities. 11 | 12 | A very simple, yet working example of a valid provyfile.py:: 13 | 14 | #!/usr/bin/python 15 | # -*- coding: utf-8 -*- 16 | 17 | from provy.core import Role 18 | from provy.more.debian import UserRole, AptitudeRole 19 | 20 | class SimpleServer(Role): 21 | def provision(self): 22 | with self.using(UserRole) as role: 23 | role.ensure_user('my-user', identified_by='my-pass', is_admin=True) 24 | 25 | with self.using(AptitudeRole) as role: 26 | role.ensure_package_installed('vim') 27 | 28 | servers = { 29 | 'frontend': { 30 | 'address': '33.33.33.33', 31 | 'user': 'root', 32 | 'roles': [ 33 | SimpleServer 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /tests/unit/core/test_runner.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.core.errors import ConfigurationError 4 | from provy.core.runner import get_items, recurse_items 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class RunnerTest(ProvyTestCase): 9 | @istest 10 | def cannot_get_items_if_prov_doesnt_have_required_attribute(self): 11 | self.assertRaises(ConfigurationError, get_items, 'some prov variable', 'inexistant_item_name', 'inexistant_item_key', 'some test func') 12 | 13 | @istest 14 | def builds_items_list_after_recursing_over_a_dict(self): 15 | 16 | def foo_macher(item): 17 | return 'foo' in item 18 | 19 | found_items = [] 20 | 21 | collection = { 22 | 'my name': 'foo name', 23 | 'personal stuff': { 24 | 'car': 'some foo truck', 25 | 'books': ['foo', 'bar'], 26 | 'bar': 'something with iron, just ignore', 27 | 'others': { 28 | 'foo': 'something undescribable', 29 | }, 30 | }, 31 | } 32 | 33 | recurse_items(collection, foo_macher, found_items) 34 | 35 | expected_items = [ 36 | 'foo name', 37 | 'some foo truck', 38 | ['foo', 'bar'], 39 | { 40 | 'foo': 'something undescribable', 41 | }, 42 | ] 43 | self.assertListEqual(sorted(found_items), sorted(expected_items), found_items) 44 | -------------------------------------------------------------------------------- /docs/source/custom-files.rst: -------------------------------------------------------------------------------- 1 | Custom Files and Templating 2 | =========================== 3 | 4 | Some methods provided by provy (including *update_file*) support passing in options that may be used in templates. 5 | 6 | *provy* uses `jinja2 `_ for templating, thus supporting if/else statements, loops and much more. It's advised that you take a look at `jinja2 `_ docs. 7 | 8 | `jinja2 `_ will look for files in two different places. The first one and probably the one you'll use the most, is a directory called *files* in the same path as *provyfile.py*. 9 | 10 | Any files you place inside this directory may be used as templates to be uploaded to the server being provisioned. Since *provy* is built on top of `fabric `_, you can use its *put* method as well to put any file or folder to the server. It's advised to use the bundled methods that come with provy, though, as those are `idempotent `_. 11 | 12 | The other place you can put files is in a *templates* directory inside Role apps. The supervisor role uses this approach, if you want to take a look at an example. If you do place files in the *templates* directory, do not forget to call the *register_template_loader* method passing in the full namespace of your app (more details in the provy.more section below). 13 | 14 | We used custom files in the :doc:`getting-started` section to provide the needed configuration files for `nginx `_. -------------------------------------------------------------------------------- /provy/more/debian/web/templates/website.init.template: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Django WebSite {{ name }} auto-start 4 | # 5 | # description: Auto-starts {{ name }}-{{ port }} 6 | # processname: {{ name }}-{{ port }} 7 | 8 | GUNICORN_DJANGO=/usr/local/bin/gunicorn_django 9 | PIDFILE={{ pid_file_path }}/{{ name }}_{{ port }}.pid 10 | 11 | case $1 in 12 | start) 13 | echo -n "Starting {{ name }}-{{ port }}: " 14 | {% if not daemon %}exec {% endif %}$GUNICORN_DJANGO --name="{{ name }}_{{ port }}" --pid="$PIDFILE" --bind="{{ host }}:{{ port }}" --workers={{ threads }} {% if daemon %}--daemon {% endif %}{% if user %}--user="{{ user }}" {% endif %} --pythonpath {{ settings_directory }}/local_settings.py 15 | echo 16 | ;; 17 | stop) 18 | echo -n "Stopping {{ name }}-{{ port }}: " 19 | if [ -f $PIDFILE ] 20 | then 21 | read PID < "$PIDFILE" 22 | 23 | if [ -d "/proc/$PID" ] 24 | then 25 | echo "{{ name }}-{{ port }} process running..." 26 | kill "$PID" && echo "{{ name }}-{{ port }} killed!" || echo "Couldn't kill $PID" 27 | else 28 | echo "{{ name }}-{{ port }} process is not running! Nothing to do!" 29 | fi 30 | else 31 | echo "{{ name }}-{{ port }} process is not running! Nothing to do!" 32 | fi 33 | echo 34 | ;; 35 | restart) 36 | echo -n "Restarting {{ name }}-{{ port }}: " 37 | /etc/init.d/{{ name }}-{{ port }} stop 38 | /etc/init.d/{{ name }}-{{ port }} start 39 | echo 40 | ;; 41 | esac 42 | -------------------------------------------------------------------------------- /provy/more/centos/utils/hostname.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide hostname utilities methods within CentOS distributions. 6 | ''' 7 | from fabric.contrib.files import sed 8 | from fabric.api import settings, hide 9 | 10 | from provy.core import Role 11 | 12 | 13 | class HostNameRole(Role): 14 | def ensure_hostname(self, hostname): 15 | 16 | ''' 17 | Ensure a fixed hostname is configured in the server. 18 | 19 | :param hostname: Hostname to be created. 20 | :type hostname: :class:`str` 21 | 22 | Example: 23 | :: 24 | 25 | class MySampleRole(Role): 26 | def provision(self): 27 | with self.using(HostNameRole) as role: 28 | role.ensure_hostname('rabbit') 29 | ''' 30 | 31 | if hostname == self.execute('hostname'): 32 | return False 33 | 34 | path = '/etc/sysconfig/network' 35 | 36 | file = self.read_remote_file(path) 37 | hostname_line = 'HOSTNAME={0}'.format(hostname) 38 | 39 | self.log('Setting up hostname') 40 | 41 | if 'HOSTNAME' not in file: 42 | self.ensure_line(hostname_line, stdout=False, sudo=True) 43 | else: 44 | with settings(hide('warnings', 'running', 'stdout')): 45 | sed(path, 'HOSTNAME=.*', hostname_line, use_sudo=True) 46 | 47 | self.execute( 48 | 'hostname "{0}"'.format(hostname), stdout=False, sudo=True, 49 | ) 50 | self.log('Hostname %s added' % hostname) 51 | return True 52 | -------------------------------------------------------------------------------- /tests/functional/core/test_runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from mock import patch 4 | from nose.tools import istest 5 | 6 | from provy.core.runner import run 7 | import provy.core.utils 8 | from tests.unit.tools.helpers import ProvyTestCase 9 | from tests.functional.fixtures.provyfile import ( 10 | provisions, 11 | cleanups, 12 | contexts, 13 | Role1, 14 | Role2, 15 | Role3, 16 | Role4, 17 | ) 18 | 19 | 20 | class RunnerTest(ProvyTestCase): 21 | @istest 22 | def runs_normal_provisioning(self): 23 | provfile_path = os.path.join('tests', 'functional', 'fixtures', 'provyfile') 24 | password = 'some-pass' 25 | extra_options = { 26 | 'password': 'another pass', 27 | } 28 | 29 | with patch.object(provy.core.utils, 'getpass') as mock_getpass: 30 | mock_getpass.return_value = 'some-password' 31 | run(provfile_path, 'test', password, extra_options) 32 | run(provfile_path, 'test2', password, extra_options) 33 | 34 | self.assertIn(Role1, provisions) 35 | self.assertIn(Role2, provisions) 36 | self.assertIn(Role3, provisions) 37 | self.assertIn(Role4, provisions) 38 | 39 | self.assertIn(Role1, cleanups) 40 | self.assertIn(Role2, cleanups) 41 | self.assertIn(Role3, cleanups) 42 | self.assertIn(Role4, cleanups) 43 | 44 | self.assertIn('foo', contexts[Role1]) 45 | self.assertIn('bar', contexts[Role2]) 46 | self.assertIn('baz', contexts[Role2]) 47 | self.assertIn('bar', contexts[Role3]) 48 | self.assertIn('baz', contexts[Role3]) 49 | self.assertIn('foo', contexts[Role4]) 50 | -------------------------------------------------------------------------------- /provy/more/debian/users/passwd_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import crypt 4 | from random import SystemRandom 5 | 6 | 7 | def random_salt_function(salt_len=12): 8 | """ 9 | Creates random salt for password. 10 | 11 | :param salt_len: Length of salt. Default :data:`12` 12 | :type param: :class:`int` 13 | 14 | :return: Computed salt 15 | :rtype: str 16 | """ 17 | charset = "abcdefghijklmnopqrstuxyz" 18 | charset = charset + charset.upper() + '1234567890' 19 | chars = [] 20 | rand = SystemRandom() 21 | for _ in range(salt_len): 22 | chars.append(rand.choice(charset)) 23 | return "".join(chars) 24 | 25 | 26 | def hash_password_function(password, salt=None, magic="6"): 27 | """ 28 | Hashes password using `crypt` function on local machine (which is not harmfull, 29 | since these hashes are well-specified. 30 | 31 | :param password: Plaintext password to be hashed. 32 | :type password: :class:`str` 33 | :param salt: Salt to be used with this password, if None will 34 | use random password. 35 | :type salt: :class:`str` 36 | :param magic: Specifies salt type. Default :data:`6` which means 37 | use `sha-512`. For all appropriate values refer 38 | to http://man7.org/linux/man-pages/man3/crypt.3.html#NOTES, or 39 | (even better) consult `man 3 crypt` on your system. 40 | :type salt: :class:`str` or :class:`int` 41 | 42 | :return: remote password 43 | """ 44 | 45 | magic = str(magic) 46 | 47 | if salt is None: 48 | salt = random_salt_function() 49 | 50 | salt = "${magic}${salt}".format(magic=magic, salt=salt) 51 | 52 | return crypt.crypt(password, salt) 53 | -------------------------------------------------------------------------------- /provy/more/debian/web/tornado.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `Tornado `_ app server utility methods for Debian distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.debian.package.aptitude import AptitudeRole 10 | from provy.more.debian.package.pip import PipRole 11 | 12 | 13 | class TornadoRole(Role): 14 | ''' 15 | This role provides `Tornado `_ app server management utilities for Debian distributions. 16 | 17 | Example: 18 | :: 19 | 20 | from provy.core import Role 21 | from provy.more.debian import TornadoRole 22 | 23 | class MySampleRole(Role): 24 | def provision(self): 25 | self.provision_role(TornadoRole) 26 | ''' 27 | 28 | def provision(self): 29 | ''' 30 | Installs `Tornado `_ and its dependencies. 31 | This method should be called upon if overriden in base classes, or `Tornado `_ won't work properly in the remote server. 32 | 33 | Example: 34 | :: 35 | 36 | from provy.core import Role 37 | from provy.more.debian import TornadoRole 38 | 39 | class MySampleRole(Role): 40 | def provision(self): 41 | self.provision_role(TornadoRole) 42 | ''' 43 | 44 | with self.using(AptitudeRole) as role: 45 | role.ensure_up_to_date() 46 | role.ensure_package_installed('python-pycurl') 47 | 48 | with self.using(PipRole) as role: 49 | role.ensure_package_installed('tornado') 50 | -------------------------------------------------------------------------------- /tests/unit/more/debian/users/test_passwd_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | from nose.tools import istest 5 | from mock import patch 6 | from provy.more.debian.users.passwd_utils import random_salt_function, hash_password_function 7 | 8 | 9 | class PasswdUtilsTest(unittest.TestCase): 10 | 11 | @istest 12 | def check_if_two_generated_salts_are_different(self): 13 | """ 14 | Instead of checking if output is truly random, we'll just check if 15 | in two conseutive calls different functions will be returned 16 | """ 17 | self.assertNotEqual(random_salt_function(), random_salt_function()) 18 | 19 | @istest 20 | def check_random_add_function_output_is_as_specified(self): 21 | self.assertEqual(len(random_salt_function(salt_len=125)), 125) 22 | 23 | @istest 24 | def check_crypt_function_gives_expected_output_for_known_magic_and_salt(self): 25 | password = "foobarbaz" 26 | expected_hash = "$6$SqAoXRvk$spgLlL/WL/vcb16ZZ4cMdF5uN90IjH0PpYKdMhqyW.BxXJEVc5RyvnpWcT.OKKJO2vsp32.CWDEd45K6r05bL0" 27 | salt = "SqAoXRvk" 28 | 29 | self.assertEqual(expected_hash, hash_password_function(password, salt)) 30 | 31 | @istest 32 | def check_crypt_function_uses_random_salt(self): 33 | password = "foobarbaz" 34 | expected_hash = "$6$SqAoXRvk$spgLlL/WL/vcb16ZZ4cMdF5uN90IjH0PpYKdMhqyW.BxXJEVc5RyvnpWcT.OKKJO2vsp32.CWDEd45K6r05bL0" 35 | salt = "SqAoXRvk" 36 | 37 | with patch("provy.more.debian.users.passwd_utils.random_salt_function") as rnd: 38 | rnd.return_value = salt 39 | self.assertEqual(expected_hash, hash_password_function(password)) 40 | self.assertTrue(rnd.called) 41 | -------------------------------------------------------------------------------- /provy/more/debian/monitoring/templates/supervisord.conf.template: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file=/tmp/supervisor.sock 3 | 4 | [supervisord] 5 | logfile={{ log_file }} 6 | logfile_maxbytes={{ log_file_max_mb }}MB 7 | logfile_backups={{ log_file_backups }} 8 | loglevel={{ log_level }} 9 | pidfile={{ pidfile }} 10 | nodaemon=false 11 | minfds=1024 12 | minprocs=200 13 | user={{ user }} 14 | 15 | [rpcinterface:supervisor] 16 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 17 | 18 | [supervisorctl] 19 | serverurl=unix:///tmp/supervisor.sock 20 | 21 | {% for program in programs %} 22 | [program:{{ program.name }}] 23 | command={{ program.command }} 24 | process_name={{ program.name }}{% if program.number_of_processes > 1 %}_%(process_num)s{% endif %} 25 | numprocs={{ program.number_of_processes }} 26 | directory={{ program.directory }} 27 | {% if program.priority %} 28 | priority={{ program.priority }} 29 | {% endif %} 30 | autostart={{ program.auto_start | lower }} 31 | autorestart={{ program.auto_restart | lower }} 32 | startretries={{ program.start_retries }} 33 | stopsignal={{ program.stop_signal }} 34 | {% if program.user %} 35 | user={{ program.user }} 36 | {% endif %} 37 | redirect_stderr=false 38 | stdout_logfile={{ program.log_folder}}/{{ program.name }}.stdout.%(process_num)s.log 39 | stdout_logfile_maxbytes={{ program.log_file_max_mb }}MB 40 | stdout_logfile_backups={{ program.log_file_backups }} 41 | stderr_logfile={{ program.log_folder}}/{{ program.name }}.stderr.%(process_num)s.log 42 | stderr_logfile_maxbytes={{ program.log_file_max_mb }}MB 43 | stderr_logfile_backups={{ program.log_file_backups }} 44 | 45 | {% if program.environment %} 46 | environment={{ program.environment }} 47 | {% endif %} 48 | {% endfor %} 49 | -------------------------------------------------------------------------------- /tests/end_to_end/provy-e2e-key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA+qwdHuztJDiH0JgoXtrwxMmyN44zEzOK4idAuwMEtcu7xJOg 3 | U0qC7mb913C8iSvhHLOPWq/ATs0cOSQ3Qh0aLCk0TBUT8RibdMicFELc8wkOCcu8 4 | OmRIjW1VO57o8s887UG2TdFyS9+hdsoQYuIYpoSX89rk67x4XDmWCuye2szo+Hf6 5 | yixlSZ72TzX5FgsBImlIcrtfGZXsuL4t4JR11cpyzcfneju4CbxQNGb6dqnGheTR 6 | 4mDJ2X0Oa4VnNnYaH/M5NZv/7fW1JU4Fwq1JY5g/3wSe76TLC9lt9QVs/IHEV2ld 7 | yJv3HfmZrHrss0kugrG2pkGM7KdntuHZlDU9DwIDAQABAoIBAH5TLF3AYoWlY3RQ 8 | qc+boEhbqM9sfvrHN89enrVgAQiowlh/WQWAgFkqV/QxYSHzlf+D0dOOzGgp33ZA 9 | dQSBbAYjQbKx0Jnon7cLvfRL+dMUlhmDrbjzfsie95wTKivrGjYqrneq/GGWMmWN 10 | 0RI0BN4t5fHDNyhBk5pOfvnEYw25xL57zeog0Vrj0FE13zXa7JP3yft7TUlRD0Rv 11 | Mg1C+OVACrqy+hfwtQUDsM/HlndpqFJk32xZcQj3MrVB89OxTzAZc3EjC4piQS5D 12 | +IG6FRU2dUXh2nWj9waU1BWRM2FuJGkqa8aVbAaJ1IMt0CUH4/TwtplTH7pbNNj/ 13 | 9ExJ4EECgYEA/m/OCSemLJg87yV754ESG0YHhurYaN8IVTQ3jSHEe6NoYPg7Rxjb 14 | b8BP2QqmPwj+KNuds5IzNpBf99y644tGl+Z8LdL+RC4O+QCHXGax3cpAX3ZAuRkU 15 | DLWAj4rliKmESuyOhLmnhhRGlGsMyTV7W43QP9zcFq5EjWPJm25RyaECgYEA/DZj 16 | U7os5nZbILX7446lbeNa6XqDc/Lt05/qSCE01c2UKoIj/IcWX+oryGRzKeQnn0bp 17 | yYdKVFWtM5saSBhwK5/gitWOL9oeGP3dGGvcHVMi2rmWG85gdrI3RRGeaG5q7kHS 18 | NqWTRm3+XrBy2VA2AiuvO1mWQWhWGbhnpOODaK8CgYEA0r+xHYwl7JI9Bqk5tEwI 19 | v2aGHY6wqkzzDgAuc0wg/3geoRN7pixEto/Ik7JqeZPtUdJ3EaJroSp9E0VV19wp 20 | IPDcsugtjDPKWx3BARxe/6LjJy8/9RF8tfow/rTB4yzmU1kVw4Iz7K8mtxDkilUu 21 | VGtmrz81XeQDDEo3V/NM84ECgYAueSccSnXzkWOBR4c+iw5YjUg3NWf7eYvxLspQ 22 | uC2wJ81hd9GBurKYweGHb1r5IR1b8AksJU3A9HiRxca1+irRHwiVDHzCvIJHQJ/4 23 | 3AcXMDZ/7yqFvSVxOa2NgI2b2JzDgkVl5GSa2bHBaOoAuRL6m2oSmZNiQgN7gcs3 24 | SuTv6QKBgHGBGt1jZsLdVFX6VP8VvREIg9wnKkJ9k7sAJ1k2rxJohbrtbIgvZhf6 25 | 1zq4J8P55t104ZWgwKQ+7XFM4z1nV7d4Z2MDDS0dBz5wovG9VBoGgXfLP1/tNGY1 26 | lD87NtwdhrbTARFiuYAxh8w6/pWTPVoerAzWYxhItVm/CuV+OZOy 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/unit/fixtures/test_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtcRGPcG1mo+Vwh8o70j16bSEChkLozSFMZgjVmEl/pQetGkF 3 | WSs1ICEPdQy505XFtHC1FUuYx7At68aI2ZDhrbuN9IGLMG2tToJAuSZe53hUovHi 4 | NQJdCDvMv1OPesi53+mjEEFJBZcrJ+Ep1iE90CAjNAofyM9gquvP8CFpVA7NOK5v 5 | J8l6HR64uODXGeJPRQx//WTcvOe6ZAwrJUA1xU/qjzGJiJ6Q6GMvuvQ924R/eblM 6 | 3YLHzBYu/u4iFB8CXyx+I6ZN75C4umq2uYE0JvClF9vlFSCqp9PfUPEW+L+3CYmA 7 | AzkIfcz5Nlx4DUcBAEGq0tGdCSPV0GGWozR61wIDAQABAoIBAQCQZBucYW3/GwLP 8 | U2t0MlRPU7v0PZMWEdmg3QdtTf0dr9J4ZFkATaeFH14lEHfp0bddMI9ZHFWAg77m 9 | 5i6+Di6IkU4iJmpIjUe37xa1Pfr0C43IZzfX/kmjCcYLUrjOw/eMHRAREJuOWljI 10 | 9EAEvyFYXL86TrSTE9Hp1Su4yQHf6A97/l8YiZ4YcyHeZJ0Bv5KfL6AlqXdXqDhR 11 | 41hpc7y+p+bprpy0bsXsjjrJt4saCBlQjtTmDtagDIGGWlyj2MsdooW4ZBLrnYTo 12 | s4rzpwbyVqcOncTJB0xB7K0T3LuwZLd9LQ9dh6LNXK3slCLIcNd4uAOjq75qMpFD 13 | EaAp3L8JAoGBANf9uu7lXCAxO+FuDazJRLH9n2WuGhjKT3IwnhEw3uE8czVWQKLU 14 | IFMz8IfH+qki5cZNMgbbCBBoWV67DK6rEL50xCF/8Yz8kCCN2NMHzG52OVAjP0MK 15 | lIy2vZwgXHy6METKqma2IVxB1A5ug5Pj/Tk+d8jZcFQWgrRwE0zriiMFAoGBANdv 16 | oEew8zKBAx8d6GVxmmHC1u5zap/od6QcGqDrxO3okIVwJSLHkymU5c2P1yGUXl5q 17 | t4ZX53WTr4Jnuniqe7mKXVSicDwBo5S52g15v01aJNuosIB4w3M077Is+h4duvrT 18 | CYDHMHHOi4uAaccpPGfY1+4A4IUXdw49ZLq6w4UrAoGATmo2oJ6yaJmXRMuAuXdE 19 | sl4CrZacsN1aJHnUGSel8x3QMdADnVnn9m0H6TPII/mgc/L4s5Z3ggVwVL0R6KQA 20 | azTXM9ZQasASz07QJiVRqdTQD/EL+ZnwvnllszXoffvWpFLztGBxEh5wD2E98cY2 21 | 2757HHccmdqmTz3VM/rbZsUCgYB/GetdAIeqzzSRQire1rQ3YyU9Dzjj2NnlJ3OK 22 | Zy8LEX9aSnyOVWJ8UM13hppsxEUcvSdDik8TLiuI6zu3fxV5tKk1ipRewrTIxRFh 23 | i+eSclF2isJ/OUBOkverjh+Obwnme2WK5XmuWyY3Cm7dwnVR6zwRvdC4lMx3yT7J 24 | b/B0ewKBgQCp6Ico73n58M+N5NHip5jYBbAWGFzfIA19zhzHnb+yxm2+GvHs/UnB 25 | 6O0Cz/zcJ2oazPiJ7tINZuqmTGoZ9AdKeNcrtFUE/y5DZRfC7iWPVGySbnUQ3PSV 26 | iC2OHqKoQsZgS86P9Z2FxGSs1zLBM49ioZxcqBHi/apgL3dAmcvmNQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /provy/core/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Core utilities. 6 | 7 | Provides the :class:`AskFor` class, which is used to prompt for a password. 8 | ''' 9 | 10 | from getpass import getpass 11 | import os 12 | 13 | 14 | def provyfile_path_from(args): 15 | if args: 16 | path = args[0] 17 | if not os.path.exists(path): 18 | raise IOError('provy file "%s" does not exist.' % path) 19 | if os.path.isabs(path): 20 | raise ValueError('provy file "%s" is absolute. Please provide a path that is relative to the current working directory.') 21 | return path 22 | elif os.path.exists('provyfile.py'): 23 | return 'provyfile.py' 24 | elif os.path.exists('provy_file.py'): 25 | return 'provy_file.py' 26 | raise IOError('No provyfile was found. Please specify an existant provyfile path, or create either a "provyfile.py" or "provy_file.py"') 27 | 28 | 29 | def provyfile_module_from(path): 30 | (base, ext) = os.path.splitext(path) 31 | base = base.replace('/', '.') 32 | return base 33 | 34 | 35 | def import_module(module_name): 36 | module = __import__(module_name) 37 | if '.' in module_name: 38 | return reduce(getattr, module_name.split('.')[1:], module) 39 | return module 40 | 41 | 42 | class AskFor(object): 43 | ''' 44 | Responsible for prompting for a password to the user. 45 | 46 | You may pass an instance of it, instead of a plain value, in the ``servers`` dictionary, so that you require the user to enter a value. 47 | ''' 48 | def __init__(self, key, question): 49 | self.key = key 50 | self.question = question 51 | 52 | def get_value(self, server): 53 | value = getpass("[Server at %s] - %s: " % (server['address'], self.question)) 54 | return value 55 | -------------------------------------------------------------------------------- /docs/source/installing.rst: -------------------------------------------------------------------------------- 1 | Installing provy 2 | ================ 3 | Before installing provy you will need to ensure you have swig installed, as m2crypto needs it. Here is how to install it in some platforms. 4 | 5 | Install the OS dependencies 6 | --------------------------- 7 | 8 | MacOSX 9 | ++++++ 10 | To install swig on a mac, the easiest way is to install using the `homebrew package manager `_ (which we will not cover here). After installing it, execute this command:: 11 | 12 | brew install https://raw.github.com/cobrateam/formulae/master/swig.rb 13 | 14 | Ubuntu and Debian GNU/Linux 15 | +++++++++++++++++++++++++++ 16 | It is just an apt-get install away =) :: 17 | 18 | $ sudo apt-get install swig 19 | 20 | Arch Linux 21 | ++++++++++ 22 | Swig is in the extra repository and can be installed with:: 23 | 24 | $ sudo pacman -S swig 25 | 26 | Other platforms 27 | +++++++++++++++ 28 | If your platform is not listed above, try searching in your package manager for *swig* and install it given the search results. 29 | 30 | Install provy 31 | ------------- 32 | Now that you have *swig*, installing *provy* is as easy as:: 33 | 34 | $ pip install provy 35 | 36 | It can be easily installed from source as well, like this:: 37 | 38 | $ # make sure fabric, jinja2 and m2crypto are installed 39 | $ pip install fabric 40 | $ pip install jinja2 41 | $ pip install m2crypto 42 | 43 | $ # now actually installing it 44 | $ git clone git@github.com:heynemann/provy.git 45 | $ python setup.py install 46 | $ provy --version 47 | 48 | As can be seen above, after being installed a *provy* command becomes available. 49 | 50 | provy is `FOSS `_ and you can find its source code at `its github page `_. -------------------------------------------------------------------------------- /tests/unit/more/centos/database/fixtures.py: -------------------------------------------------------------------------------- 1 | FOO_DB_WITH_JOHN_GRANTS = """ 2 | *************************** 1. row *************************** 3 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*B9EE00DF55E7C816911C6DA56F1E3A37BDB31093' 4 | *************************** 2. row *************************** 5 | Grants for john@%: GRANT ALL PRIVILEGES ON `foo`.* TO 'john'@'%' 6 | """ 7 | 8 | 9 | FOO_DB_WITHOUT_JOHN_GRANTS = """ 10 | *************************** 1. row *************************** 11 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*B9EE00DF55E7C816911C6DA56F1E3A37BDB31093' 12 | """ 13 | 14 | 15 | FOO_DB_WITH_JOHN_GRANTS_AND_GRANT_OPTION = """ 16 | *************************** 1. row *************************** 17 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' 18 | *************************** 2. row *************************** 19 | Grants for john@%: GRANT ALL PRIVILEGES ON `foo`.* TO 'john'@'%' WITH GRANT OPTION 20 | """ 21 | 22 | 23 | HOSTS_FOR_USER = """ 24 | *************************** 1. row *************************** 25 | Host: 127.0.0.1 26 | *************************** 2. row *************************** 27 | Host: ::1 28 | *************************** 3. row *************************** 29 | Host: my-desktop 30 | *************************** 4. row *************************** 31 | Host: localhost 32 | """ 33 | 34 | 35 | DATABASES = """ 36 | *************************** 1. row *************************** 37 | Database: information_schema 38 | *************************** 2. row *************************** 39 | Database: mysql 40 | *************************** 3. row *************************** 41 | Database: performance_schema 42 | *************************** 4. row *************************** 43 | Database: test 44 | """ 45 | -------------------------------------------------------------------------------- /tests/unit/more/debian/database/fixtures.py: -------------------------------------------------------------------------------- 1 | FOO_DB_WITH_JOHN_GRANTS = """ 2 | *************************** 1. row *************************** 3 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*B9EE00DF55E7C816911C6DA56F1E3A37BDB31093' 4 | *************************** 2. row *************************** 5 | Grants for john@%: GRANT ALL PRIVILEGES ON `foo`.* TO 'john'@'%' 6 | """ 7 | 8 | 9 | FOO_DB_WITHOUT_JOHN_GRANTS = """ 10 | *************************** 1. row *************************** 11 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*B9EE00DF55E7C816911C6DA56F1E3A37BDB31093' 12 | """ 13 | 14 | 15 | FOO_DB_WITH_JOHN_GRANTS_AND_GRANT_OPTION = """ 16 | *************************** 1. row *************************** 17 | Grants for john@%: GRANT USAGE ON *.* TO 'john'@'%' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' 18 | *************************** 2. row *************************** 19 | Grants for john@%: GRANT ALL PRIVILEGES ON `foo`.* TO 'john'@'%' WITH GRANT OPTION 20 | """ 21 | 22 | 23 | HOSTS_FOR_USER = """ 24 | *************************** 1. row *************************** 25 | Host: 127.0.0.1 26 | *************************** 2. row *************************** 27 | Host: ::1 28 | *************************** 3. row *************************** 29 | Host: my-desktop 30 | *************************** 4. row *************************** 31 | Host: localhost 32 | """ 33 | 34 | 35 | DATABASES = """ 36 | *************************** 1. row *************************** 37 | Database: information_schema 38 | *************************** 2. row *************************** 39 | Database: mysql 40 | *************************** 3. row *************************** 41 | Database: performance_schema 42 | *************************** 4. row *************************** 43 | Database: test 44 | """ 45 | -------------------------------------------------------------------------------- /tests/unit/more/debian/programming/test_php.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import AptitudeRole, PHPRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class PHPRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(PHPRoleTest, self).setUp() 11 | self.role = PHPRole(prov=None, context={}) 12 | 13 | @istest 14 | def adds_repositories_and_installs_necessary_packages_to_provision_to_debian(self): 15 | with self.using_stub(AptitudeRole) as mock_aptitude, self.provisioning_to('debian'): 16 | self.role.provision() 17 | 18 | source_calls = mock_aptitude.ensure_aptitude_source.mock_calls 19 | self.assertEqual(source_calls, [ 20 | call('deb http://packages.dotdeb.org squeeze all'), 21 | call('deb-src http://packages.dotdeb.org squeeze all'), 22 | ]) 23 | 24 | mock_aptitude.ensure_gpg_key.assert_called_with('http://www.dotdeb.org/dotdeb.gpg') 25 | self.assertTrue(mock_aptitude.force_update.called) 26 | 27 | install_calls = mock_aptitude.ensure_package_installed.mock_calls 28 | self.assertEqual(install_calls, [call('php5-dev'), call('php5-fpm'), call('php-pear')]) 29 | 30 | @istest 31 | def provisions_to_ubuntu_without_adding_repositories(self): 32 | with self.using_stub(AptitudeRole) as mock_aptitude, self.provisioning_to('ubuntu'): 33 | self.role.provision() 34 | 35 | self.assertFalse(mock_aptitude.ensure_aptitude_source.called) 36 | self.assertFalse(mock_aptitude.ensure_gpg_key.called) 37 | self.assertFalse(mock_aptitude.force_update.called) 38 | 39 | install_calls = mock_aptitude.ensure_package_installed.mock_calls 40 | self.assertEqual(install_calls, [call('php5-dev'), call('php5-fpm'), call('php-pear')]) 41 | -------------------------------------------------------------------------------- /provy/more/debian/cache/templates/memcached.conf.template: -------------------------------------------------------------------------------- 1 | # 2003 - Jay Bonci 2 | # This configuration file is read by the start-memcached script provided as 3 | # part of the Debian GNU/Linux distribution. 4 | 5 | # Run memcached as a daemon. This command is implied, and is not needed for the 6 | # daemon to run. See the README.Debian that comes with this package for more 7 | # information. 8 | -d 9 | 10 | # Log memcached's output to /var/log/memcached 11 | logfile {{ log_folder }}/memcached.log 12 | 13 | {% if verbose_level == 1 %} 14 | # Be verbose 15 | -v 16 | {% endif %} 17 | 18 | {% if verbose_level == 2 %} 19 | # Be even more verbose (print client commands as well) 20 | -vv 21 | {% endif %} 22 | 23 | # Start with a cap of 64 megs of memory. It's reasonable, and the daemon default 24 | # Note that the daemon will grow to this size, but does not start out holding this much 25 | # memory 26 | -m {{ memory_in_mb }} 27 | 28 | # Default connection port is 11211 29 | -p {{ port }} 30 | 31 | # Run the daemon as root. The start-memcached will default to running as root if no 32 | # -u command is present in this config file 33 | -u {{ user }} 34 | 35 | # Specify which IP address to listen on. The default is to listen on all IP addresses 36 | # This parameter is one of the only security measures that memcached has, so make sure 37 | # it's listening on a firewalled interface. 38 | -l {{ host }} 39 | 40 | # Limit the number of simultaneous incoming connections. The daemon default is 1024 41 | -c {{ simultaneous_connections }} 42 | 43 | {% if lock_down %} 44 | # Lock down all paged memory. Consult with the README and homepage before you do this 45 | -k 46 | {% endif %} 47 | 48 | {% if error_when_memory_exhausted %} 49 | # Return error when memory is exhausted (rather than removing items) 50 | -M 51 | {% endif %} 52 | 53 | {% if maximize_core_file_limit %} 54 | # Maximize core file limit 55 | -r 56 | {% endif %} 57 | 58 | -------------------------------------------------------------------------------- /provy/more/debian/database/postgresql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `PostgreSQL `_ database management utilities for Debian distributions. 6 | ''' 7 | 8 | from provy.more.base.database.postgresql import BasePostgreSQLRole 9 | from provy.more.debian.package.aptitude import AptitudeRole 10 | 11 | 12 | class PostgreSQLRole(BasePostgreSQLRole): 13 | ''' 14 | This role provides `PostgreSQL `_ database management utilities for Debian distributions. 15 | 16 | Take a look at :class:`provy.more.base.database.postgresql.BasePostgreSQLRole` for more available methods. 17 | 18 | Example: 19 | :: 20 | 21 | from provy.core import Role 22 | from provy.more.debian import PostgreSQLRole 23 | 24 | class MySampleRole(Role): 25 | def provision(self): 26 | with self.using(PostgreSQLRole) as role: 27 | role.ensure_user("john") 28 | role.ensure_database("foo", owner="john") 29 | ''' 30 | def provision(self): 31 | ''' 32 | Installs `PostgreSQL `_ and its dependencies. 33 | This method should be called upon if overriden in base classes, or PostgreSQL won't work properly in the remote server. 34 | 35 | Example: 36 | :: 37 | 38 | class MySampleRole(Role): 39 | def provision(self): 40 | self.provision_role(PostgreSQLRole) # no need to call this if using with block. 41 | ''' 42 | with self.using(AptitudeRole) as role: 43 | role.ensure_package_installed('postgresql') 44 | role.ensure_package_installed('postgresql-server-dev-%s' % self.__get_version()) 45 | 46 | def __get_version(self): 47 | distro = self.get_distro_info() 48 | if distro.distributor_id.lower() == 'ubuntu': 49 | version = '9.2' 50 | else: 51 | version = '8.4' 52 | return version 53 | -------------------------------------------------------------------------------- /tests/end_to_end/Makefile: -------------------------------------------------------------------------------- 1 | # User creation vars 2 | export PROVY_USERNAME=provy 3 | PROVY_GROUP=provy 4 | export PROVY_PASSWORD=provy 5 | PROVY_HOME=/home/provy 6 | PROVY_SHELL=/bin/bash 7 | PROVY_ADMIN_GROUP=sudo 8 | PROVY_PUBLIC_KEY=provy-e2e-key.pub 9 | export PROVY_LOCAL_PRIVATE_KEY=$(PWD)/provy-e2e-key 10 | PROVY_SSH_DIR=$(PROVY_HOME)/.ssh 11 | PROVY_AUTHORIZED_KEYS=$(PROVY_SSH_DIR)/authorized_keys 12 | VAGRANT_KEYS=$(shell dirname $(shell gem which vagrant))/../keys 13 | VAGRANT_PRIVATE_KEY=$(VAGRANT_KEYS)/vagrant 14 | 15 | # Network vars 16 | export PROVY_HOST=33.33.33.33 17 | export PROVY_PORT=8888 18 | KNOWN_HOSTS=$(HOME)/.ssh/known_hosts 19 | 20 | # Commands 21 | VAGRANT_SSH=vagrant ssh end_to_end -c 22 | PROJECT_ROOT=$(shell dirname $(shell dirname $(PWD))) 23 | export PYTHONPATH=$(PROJECT_ROOT) 24 | PROVY=python ../../provy/console.py 25 | 26 | 27 | end-to-end: 28 | @echo Starting end-to-end tests 29 | @make destroy-vm start-vm prepare-vm run-tests 30 | 31 | destroy-vm: 32 | @echo Destroying previous VM 33 | @vagrant destroy -f end_to_end 34 | 35 | start-vm: 36 | @echo Starting a new VM 37 | @vagrant up end_to_end 38 | 39 | prepare-vm: 40 | @echo Preparing VM for the tests 41 | @make prepare-user 42 | @make provision 43 | 44 | prepare-user: 45 | @echo Preparing user 46 | @$(VAGRANT_SSH) "sudo groupadd $(PROVY_GROUP); sudo useradd -g $(PROVY_GROUP) -s $(PROVY_SHELL) -m $(PROVY_USERNAME) -G $(PROVY_ADMIN_GROUP)" 47 | @$(VAGRANT_SSH) "echo $(PROVY_USERNAME):$(PROVY_PASSWORD) | sudo chpasswd" 48 | @$(VAGRANT_SSH) "sudo mkdir -p $(PROVY_SSH_DIR)" 49 | @$(VAGRANT_SSH) "sudo chmod 0700 $(PROVY_SSH_DIR)" 50 | @scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $(VAGRANT_PRIVATE_KEY) $(PROVY_PUBLIC_KEY) vagrant@$(PROVY_HOST): 51 | @$(VAGRANT_SSH) "sudo mv $(PROVY_PUBLIC_KEY) $(PROVY_AUTHORIZED_KEYS)" 52 | @$(VAGRANT_SSH) "sudo chmod 0640 $(PROVY_AUTHORIZED_KEYS)" 53 | @$(VAGRANT_SSH) "sudo chown -R $(PROVY_USERNAME):$(PROVY_GROUP) $(PROVY_HOME)" 54 | -@ssh-keygen -f "$(KNOWN_HOSTS)" -R $(PROVY_HOST) 55 | -@ssh-keyscan -t rsa,dsa $(PROVY_HOST) >> $(KNOWN_HOSTS) 56 | 57 | provision: 58 | @$(PROVY) -s end-to-end -p $(PROVY_PASSWORD) 59 | @echo Sleeping for a while to let supervisor start... 60 | @sleep 5 61 | 62 | run-tests: 63 | @echo Running the tests 64 | @nosetests test.py 65 | -------------------------------------------------------------------------------- /tests/end_to_end/provyfile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from provy.core import Role 6 | from provy.more.debian import UserRole, TornadoRole, SupervisorRole, NginxRole 7 | 8 | 9 | class FrontEnd(Role): 10 | def provision(self): 11 | with self.using(UserRole) as role: 12 | role.ensure_user('frontend', identified_by='pass', is_admin=True) 13 | 14 | with self.using(NginxRole) as role: 15 | role.ensure_conf(conf_template='nginx.conf', options={'user': 'frontend'}) 16 | role.ensure_site_disabled('default') 17 | role.create_site(site='website', template='website', options={ 18 | 'host': os.environ['PROVY_HOST'], 19 | 'port': os.environ['PROVY_PORT'], 20 | }) 21 | role.ensure_site_enabled('website') 22 | 23 | 24 | class BackEnd(Role): 25 | def provision(self): 26 | with self.using(UserRole) as role: 27 | role.ensure_user('backend', identified_by='pass', is_admin=True) 28 | 29 | self.update_file('website.py', '/home/backend/website.py', owner='backend', sudo=True) 30 | 31 | self.provision_role(TornadoRole) 32 | 33 | # make sure we have a folder to store our logs 34 | self.ensure_dir('/home/backend/logs', owner='backend') 35 | 36 | with self.using(SupervisorRole) as role: 37 | role.config( 38 | config_file_directory='/home/backend', 39 | log_folder='/home/backend/logs/', 40 | user='backend' 41 | ) 42 | 43 | with role.with_program('website') as program: 44 | program.directory = '/home/backend' 45 | program.command = 'python website.py 800%(process_num)s' 46 | program.number_of_processes = 4 47 | 48 | program.log_folder = '/home/backend/logs' 49 | 50 | 51 | servers = { 52 | 'end-to-end': { 53 | 'frontend': { 54 | 'address': os.environ['PROVY_HOST'], 55 | 'user': os.environ['PROVY_USERNAME'], 56 | 'roles': [ 57 | FrontEnd 58 | ] 59 | }, 60 | 'backend': { 61 | 'address': os.environ['PROVY_HOST'], 62 | 'user': os.environ['PROVY_USERNAME'], 63 | 'roles': [ 64 | BackEnd 65 | ] 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/source/provyfile.rst: -------------------------------------------------------------------------------- 1 | provyfile and Runtime Arguments 2 | =============================== 3 | 4 | *provy* uses a python module called *provyfile.py* in order to retrieve the definitions of your roles, servers and the relationships between them. 5 | 6 | .. image:: images/provyfile.png 7 | 8 | This is the overall structure of a *provy* provisioning file. The first imports will be very similar among most your provyfile.py files. You'll always import Role and most of the time will use one of the other built-in roles. 9 | 10 | After the imports, come your *Role* Definitions. This is where you'll specify how you want your servers to be built. You can find more about how to build roles in the :doc:`what-are-roles` and :doc:`using-roles` sections. 11 | 12 | Last but not least, you describe your servers and how they relate to roles. This brings us to the *AskFor* parameter (*provy.core.AskFor*). This class allows you to specify that a given option for a given server should be filled at runtime, either by passing in the command line or by asking the user doing the provisioning. 13 | 14 | *AskFor* takes two arguments: *key* and *question*. The *key* argument is needed to allow passing the argument when running *provy* (more on that in the next section). The *question* is used when *provy* asks the user running it for the parameter. 15 | 16 | *AskFor* is really useful for sensitive data such as passwords. You don't want to expose this data in your provyfile in plain text. You just use an *AskFor* parameter for it and supply the information at runtime. Let's look at a sample of *AskFor* usage. :: 17 | 18 | servers = { 19 | 'frontend': { 20 | 'address': '33.33.33.33', 21 | 'user': 'vagrant', 22 | 'roles': [ 23 | FrontEnd 24 | ], 25 | 'options': { 26 | 'mysql-db-password': 27 | AskFor('mysql-db-password', 28 | 'Please enter the password for the app database') 29 | } 30 | } 31 | } 32 | 33 | This parameter can be supplied twofold: if you don't specify it in the console when calling *provy*, you will be asked for it. If you need to specify it in the console, just use its key like this:: 34 | 35 | provy -s server -p password mysql-db-password=somepass 36 | 37 | All arguments must take this form of key=value, with no spaces. The key must be exactly the same, case-sensitive. -------------------------------------------------------------------------------- /provy/more/debian/programming/php.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `PHP `_ utilities for Debian and Ubuntu distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.debian.package.aptitude import AptitudeRole 10 | 11 | 12 | class PHPRole(Role): 13 | ''' 14 | This role provides `PHP `_ utilities for Debian distributions. 15 | 16 | Additionally, installs php5-dev (PHP source libraries), php-pear (PHP package management) and php5-fpm (FastCGI implementation for PHP which can be used with Nginx). 17 | 18 | Example: 19 | :: 20 | 21 | from provy.core import Role 22 | from provy.more.debian import PHPRole 23 | 24 | class MySampleRole(Role): 25 | def provision(self): 26 | self.provision_role(PHPRole) 27 | ''' 28 | 29 | def provision(self): 30 | ''' 31 | Installs PHP 5 (probably 5.3, depending on your server) and its dependencies. 32 | 33 | If your server is a Debian (non-derived) machine, it also adds the `dotdeb `_ repositories for PHP 5.3, 34 | so that you can use them with :class:`AptitudeRole ` to install what you need. 35 | 36 | This method should be called upon if overriden in base classes, or PHP won't work properly in the remote server. 37 | 38 | Example: 39 | :: 40 | 41 | from provy.core import Role 42 | from provy.more.debian import PHPRole 43 | 44 | class MySampleRole(Role): 45 | def provision(self): 46 | self.provision_role(PHPRole) # no need to call this if using with block. 47 | ''' 48 | 49 | with self.using(AptitudeRole) as aptitude: 50 | self.__prepare_repositories(aptitude) 51 | 52 | aptitude.ensure_package_installed('php5-dev') 53 | aptitude.ensure_package_installed('php5-fpm') 54 | aptitude.ensure_package_installed('php-pear') 55 | 56 | def __prepare_repositories(self, aptitude): 57 | distro_info = self.get_distro_info() 58 | if distro_info.distributor_id == 'Debian': 59 | aptitude.ensure_aptitude_source('deb http://packages.dotdeb.org squeeze all') 60 | aptitude.ensure_aptitude_source('deb-src http://packages.dotdeb.org squeeze all') 61 | 62 | aptitude.ensure_gpg_key('http://www.dotdeb.org/dotdeb.gpg') 63 | aptitude.force_update() 64 | -------------------------------------------------------------------------------- /tests/functional/fixtures/provyfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from provy.core import Role, AskFor 5 | 6 | 7 | provisions = [] 8 | cleanups = [] 9 | contexts = {} 10 | 11 | 12 | class Role1(Role): 13 | def provision(self): 14 | provisions.append(self.__class__) 15 | contexts[self.__class__] = self.context 16 | self.context['cleanup'].extend([ 17 | Role2(self.prov, self.context), 18 | Role3(self.prov, self.context), 19 | ]) 20 | 21 | def cleanup(self): 22 | super(Role1, self).cleanup() 23 | cleanups.append(self.__class__) 24 | 25 | 26 | class Role2(Role): 27 | def provision(self): 28 | provisions.append(self.__class__) 29 | contexts[self.__class__] = self.context 30 | 31 | def cleanup(self): 32 | super(Role2, self).cleanup() 33 | cleanups.append(self.__class__) 34 | 35 | 36 | class Role3(Role): 37 | def provision(self): 38 | provisions.append(self.__class__) 39 | contexts[self.__class__] = self.context 40 | 41 | def cleanup(self): 42 | super(Role3, self).cleanup() 43 | cleanups.append(self.__class__) 44 | 45 | 46 | class Role4(Role): 47 | def provision(self): 48 | provisions.append(self.__class__) 49 | contexts[self.__class__] = self.context 50 | 51 | def cleanup(self): 52 | super(Role4, self).cleanup() 53 | cleanups.append(self.__class__) 54 | 55 | servers = { 56 | 'test': { 57 | 'role1': { 58 | 'address': '33.33.33.33', 59 | 'user': 'vagrant', 60 | 'roles': [ 61 | Role1, 62 | ], 63 | 'options': { 64 | 'foo': 'FOO', 65 | 'password': AskFor('password', 'Provide a password'), 66 | 'another-password': AskFor('another-password', 'Provide another password'), 67 | }, 68 | 'ssh_key': '/some/key.pub', 69 | }, 70 | 'roles2and3': { 71 | 'address': '33.33.33.34', 72 | 'user': 'vagrant', 73 | 'roles': [ 74 | Role2, 75 | Role3, 76 | ], 77 | 'options': { 78 | 'bar': 'BAR', 79 | 'baz': 'BAZ', 80 | }, 81 | }, 82 | }, 83 | 'test2': { 84 | 'address': '33.33.33.35', 85 | 'user': 'vagrant', 86 | 'roles': [ 87 | Role4, 88 | ], 89 | 'options': { 90 | 'foo': 'FOO', 91 | }, 92 | }, 93 | } 94 | -------------------------------------------------------------------------------- /provy/console.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # provy provisioning 5 | # https://github.com/python-provy/provy 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | import sys 12 | import os 13 | import re 14 | from os.path import exists, abspath, splitext 15 | from optparse import OptionParser 16 | 17 | from provy.core import run 18 | from provy.core.utils import provyfile_path_from 19 | 20 | 21 | class Messages(object): 22 | role = """Role to provision the specified servers with. This is a recursive 23 | option""" 24 | server = """Servers to provision with the specified role. This is a 25 | recursive option.""" 26 | password = """Password to use for authentication with servers. 27 | If passwords differ from server to server this does not work.""" 28 | 29 | 30 | def __get_extra_options(): 31 | extra_options = {} 32 | if len(sys.argv) > 1: 33 | for arg in sys.argv[1:]: 34 | match = re.match('(?P.+?)=(?P.+)', arg) 35 | if match: 36 | extra_options[match.groupdict()['key']] = match.groupdict()['value'] 37 | sys.argv.remove(arg) 38 | 39 | return extra_options 40 | 41 | 42 | def __get_arguments(): 43 | parser = OptionParser() 44 | parser.add_option("-s", "--server", dest="server", help=Messages.server) 45 | parser.add_option("-p", "--password", dest="password", default=None, 46 | help=Messages.password) 47 | 48 | (options, args) = parser.parse_args() 49 | 50 | return (options, args) 51 | 52 | 53 | def __get_provy_file_path(provyfile_name): 54 | path = abspath(provyfile_name) 55 | if not exists(path): 56 | return None 57 | return splitext(path.replace(abspath('.'), '').lstrip('/').rstrip('/'))[0] 58 | 59 | 60 | def main(): 61 | sys.path.insert(0, os.curdir) 62 | 63 | extra_options = __get_extra_options() 64 | (options, args) = __get_arguments() 65 | 66 | provyfile_path = provyfile_path_from(args) 67 | 68 | if options.server is None and provyfile_path: 69 | # TODO: Improve this code to 'find' the set of servers defined in the 70 | # provyfile and run with the defined server set (if only one is defined) 71 | print "\nInfo: Provy is running using the 'test' set of servers.\n" 72 | options.server = 'test' 73 | 74 | run(provyfile_path, options.server, options.password, extra_options) 75 | 76 | if __name__ == '__main__': 77 | main() 78 | -------------------------------------------------------------------------------- /tests/unit/more/debian/users/test_ssh.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from nose.tools import istest 4 | from mock import call 5 | from jinja2 import FileSystemLoader 6 | 7 | from provy.more.debian import SSHRole 8 | from tests.unit.tools.helpers import ProvyTestCase, PROJECT_ROOT 9 | 10 | 11 | class SSHRoleTest(ProvyTestCase): 12 | def setUp(self): 13 | super(SSHRoleTest, self).setUp() 14 | self.role = SSHRole(None, {}) 15 | 16 | template_dir = os.path.join(PROJECT_ROOT, 'tests', 'unit', 'fixtures') 17 | self.role.context['loader'] = FileSystemLoader(template_dir) 18 | 19 | self.test_pub_key = open(os.path.join(template_dir, 'test_public_key')).read() 20 | self.test_private_key = self.role.render('test_private_key.pem') 21 | 22 | @istest 23 | def ensures_ssh_key(self): 24 | with self.mock_role_methods('_SSHRole__write_keys', 'ensure_dir') as (mock_write, ensure_dir): 25 | self.role.ensure_ssh_key('user', 'test_private_key.pem') 26 | 27 | ensure_dir.assert_called_with( 28 | '/home/user/.ssh', owner='user', sudo=True, 29 | ) 30 | mock_write.assert_called_with( 31 | 'user', self.test_private_key, self.test_pub_key, 32 | ) 33 | 34 | @istest 35 | def writes_keys(self): 36 | with self.mock_role_methods('execute_python', 'write_to_temp_file', 'update_file') as (execute_python, write_to_temp_file, update_file): 37 | self.role._SSHRole__write_keys('user', '..private..', '..public..') 38 | 39 | self.assertEqual( 40 | execute_python.call_args, 41 | call('import os; print os.uname()[1]', stdout=False) 42 | ) 43 | 44 | write_to_temp_file.assert_has_calls([ 45 | call('..public.. user@' + str(execute_python.return_value)), 46 | call('..private..'), 47 | ]) 48 | 49 | update_file.assert_has_calls([ 50 | call( 51 | write_to_temp_file.return_value, 52 | '/home/user/.ssh/id_rsa.pub', sudo=True, owner='user', 53 | ), 54 | call( 55 | write_to_temp_file.return_value, 56 | '/home/user/.ssh/id_rsa', sudo=True, owner='user', 57 | ), 58 | ]) 59 | 60 | @istest 61 | def doesnt_log_if_updating_keys_files_fails(self): 62 | with self.mock_role_methods('execute_python', 'write_to_temp_file', 'update_file', 'log') as (execute_python, write_to_temp_file, update_file, log): 63 | update_file.return_value = False 64 | 65 | self.role._SSHRole__write_keys('user', '..private..', '..public..') 66 | 67 | self.assertFalse(log.called) 68 | -------------------------------------------------------------------------------- /tests/unit/more/centos/vcs/test_git.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.centos import YumRole, GitRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class GitRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(GitRoleTest, self).setUp() 11 | self.role = GitRole(prov=None, context={}) 12 | 13 | @istest 14 | def ensures_a_repository_is_cloned_as_sudo(self): 15 | with self.execute_mock() as execute: 16 | self.role.ensure_repository('some-repo-url', 'working-tree-path') 17 | 18 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=True, stdout=False, user=None) 19 | 20 | @istest 21 | def ensures_a_repository_is_cloned_as_non_sudo(self): 22 | with self.execute_mock() as execute: 23 | self.role.ensure_repository('some-repo-url', 'working-tree-path', sudo=False) 24 | 25 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=False, stdout=False, user=None) 26 | 27 | @istest 28 | def ensures_a_repository_is_cloned_as_specific_user(self): 29 | with self.execute_mock() as execute, self.mock_role_method('change_path_owner') as change_path_owner: 30 | self.role.ensure_repository('some-repo-url', 'working-tree-path', owner='joe', sudo=False) 31 | 32 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=False, stdout=False, user='joe') 33 | change_path_owner.assert_called_with('working-tree-path', 'joe') 34 | 35 | @istest 36 | def installs_necessary_packages_to_provision(self): 37 | with self.using_stub(YumRole) as yum: 38 | self.role.provision() 39 | 40 | yum.ensure_up_to_date.assert_called_once_with() 41 | yum.ensure_package_installed.assert_called_once_with('git-core') 42 | 43 | @istest 44 | def ensures_a_branch_is_checked_out_if_needed(self): 45 | sudo = 'is it sudo?' 46 | owner = 'foo-owner' 47 | branch = 'some-branch' 48 | with self.mock_role_methods('remote_exists_dir', 'execute', 'change_path_owner'): 49 | self.role.remote_exists_dir.return_value = True 50 | self.role.execute.return_value = '# On branch master' 51 | 52 | self.role.ensure_repository('some-repo-url', 'working-tree-path', sudo=sudo, branch=branch, owner=owner) 53 | 54 | self.assertEqual(self.role.execute.mock_calls, [ 55 | call('git --git-dir="working-tree-path/.git" --work-tree="working-tree-path" status', sudo=True, stdout=False), 56 | call('git --git-dir="working-tree-path/.git" --work-tree="working-tree-path" checkout some-branch', sudo=sudo, user=owner), 57 | ]) 58 | -------------------------------------------------------------------------------- /tests/unit/more/debian/vcs/test_git.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import AptitudeRole, GitRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class GitRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(GitRoleTest, self).setUp() 11 | self.role = GitRole(prov=None, context={}) 12 | 13 | @istest 14 | def ensures_a_repository_is_cloned_as_sudo(self): 15 | with self.execute_mock() as execute: 16 | self.role.ensure_repository('some-repo-url', 'working-tree-path') 17 | 18 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=True, stdout=False, user=None) 19 | 20 | @istest 21 | def ensures_a_repository_is_cloned_as_non_sudo(self): 22 | with self.execute_mock() as execute: 23 | self.role.ensure_repository('some-repo-url', 'working-tree-path', sudo=False) 24 | 25 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=False, stdout=False, user=None) 26 | 27 | @istest 28 | def ensures_a_repository_is_cloned_as_specific_user(self): 29 | with self.execute_mock() as execute, self.mock_role_method('change_path_owner') as change_path_owner: 30 | self.role.ensure_repository('some-repo-url', 'working-tree-path', owner='joe', sudo=False) 31 | 32 | execute.assert_called_with('git clone some-repo-url working-tree-path', sudo=False, stdout=False, user='joe') 33 | change_path_owner.assert_called_with('working-tree-path', 'joe') 34 | 35 | @istest 36 | def installs_necessary_packages_to_provision(self): 37 | with self.using_stub(AptitudeRole) as aptitude: 38 | self.role.provision() 39 | 40 | aptitude.ensure_up_to_date.assert_called_once_with() 41 | aptitude.ensure_package_installed.assert_called_once_with('git-core') 42 | 43 | @istest 44 | def ensures_a_branch_is_checked_out_if_needed(self): 45 | sudo = 'is it sudo?' 46 | owner = 'foo-owner' 47 | branch = 'some-branch' 48 | with self.mock_role_methods('remote_exists_dir', 'execute', 'change_path_owner'): 49 | self.role.remote_exists_dir.return_value = True 50 | self.role.execute.return_value = '# On branch master' 51 | 52 | self.role.ensure_repository('some-repo-url', 'working-tree-path', sudo=sudo, branch=branch, owner=owner) 53 | 54 | self.assertEqual(self.role.execute.mock_calls, [ 55 | call('git --git-dir="working-tree-path/.git" --work-tree="working-tree-path" status', sudo=True, stdout=False), 56 | call('git --git-dir="working-tree-path/.git" --work-tree="working-tree-path" checkout some-branch', sudo=sudo, user=owner), 57 | ]) 58 | -------------------------------------------------------------------------------- /docs/source/recipes/django-1-server.rst: -------------------------------------------------------------------------------- 1 | Django + Nginx same server 2 | ========================== 3 | 4 | In this recipe we'll be running a django website with 4 processes and 2 threads per process. 5 | 6 | `Django `_, `gunicorn `_, `supervisor `_ or `nginx `_ 7 | concepts and usage are beyond the scope of this recipe. 8 | 9 | Our web server will be nginx and it will be responsible for `load balancing `_ among our django 10 | processes and for serving static files. 11 | 12 | The load balancing will be made using `reverse proxying `_ to the 4 gunicorn processes, that are bound to ports 8000-8003. 13 | 14 | The gunicorn processes will be monitored by supervisor. This is crucial to make sure that any process that fails is restarted. 15 | 16 | All logs are recorded in the user's home "logs" directory. 17 | 18 | Our user for this recipe is called `djangotutorial`. 19 | 20 | The application 21 | --------------- 22 | 23 | The application that we'll deploy is the app developed by following the `Django documentation tutorial `_. 24 | 25 | There's a public repo at https://github.com/heynemann/django-tutorial. This is the repository we'll use to deploy our application. 26 | 27 | It is a very simple application, but it serves us right in that it features django admin, static files and database access. 28 | 29 | Pre-requisites 30 | -------------- 31 | 32 | The obvious pre-requisites for our recipe is `provy`. For more instructions on how to install it, check the :doc:`Installing provy ` section in `provy`'s main docs. 33 | 34 | In this recipe we'll be using vagrant for our `local` server and amazon ec2 for our `production` server. 35 | 36 | Using vagrant is completely optional, though. If you have a local server that you provision and deploy to, feel free to replace the address and user in `provyfile` with your own data. 37 | 38 | You'll also need an account with `Amazon AWS `_. Learning how to use Amazon EC2 is out of the scope of this recipe. 39 | If you don't have a production server and you don't expect your website to have a very big hit, Amazon has a very generous `free tier `_. 40 | 41 | For the purposes of this recipe, consider we have a production server running at Amazon AWS (even if at the time you are reading this the server is not online). 42 | 43 | The deployment process 44 | ---------------------- 45 | 46 | Our deployment script (fabric) will do the following steps: 47 | 48 | a. Update git's clone in the server; 49 | b. Run syncdb against the app repo in the server; 50 | c. Run collectstatic against the app repo in the server; 51 | d. Restart supervisor after everything. 52 | 53 | This should be enough to have our app up-to-date in the webserver. -------------------------------------------------------------------------------- /provy/more/debian/users/ssh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide SSH keygen utilities for Debian distributions. 6 | ''' 7 | 8 | from os.path import join 9 | 10 | from Crypto.PublicKey import RSA 11 | 12 | from provy.core import Role 13 | 14 | 15 | class SSHRole(Role): 16 | ''' 17 | This role provides SSH keygen utilities for Debian distributions. 18 | 19 | Example: 20 | :: 21 | 22 | from provy.core import Role 23 | from provy.more.debian import SSHRole 24 | 25 | class MySampleRole(Role): 26 | def provision(self): 27 | with self.using(SSHRole) as role: 28 | role.ensure_ssh_key(user='someuser', private_key_file="private-key") 29 | ''' 30 | 31 | def ensure_ssh_key(self, user, private_key_file): 32 | ''' 33 | Ensures that the specified private ssh key is present in the remote server. Also creates the public key for this private key. 34 | 35 | The private key file must be a template and be accessible to the :meth:`Role.render ` method. 36 | 37 | :param user: Owner of the keys. 38 | :type user: :class:`str` 39 | :param private_key_file: Template file for the private key. 40 | :type private_key_file: :class:`str` 41 | 42 | Example: 43 | :: 44 | 45 | from provy.core import Role 46 | from provy.more.debian import SSHRole 47 | 48 | class MySampleRole(Role): 49 | def provision(self): 50 | with self.using(SSHRole) as role: 51 | role.ensure_ssh_key(user='someuser', private_key_file="private-key") 52 | 53 | ''' 54 | path = '/home/%s' % user 55 | ssh_path = join(path, '.ssh') 56 | self.ensure_dir(ssh_path, sudo=True, owner=user) 57 | private_key = self.render(private_key_file) 58 | 59 | key = RSA.importKey(private_key) 60 | public_key = key.publickey().exportKey(format='OpenSSH') 61 | 62 | self.__write_keys(user, private_key, public_key) 63 | 64 | def __write_keys(self, user, private_key, public_key): 65 | path = '/home/%s' % user 66 | ssh_path = join(path, '.ssh') 67 | pub_path = join(ssh_path, 'id_rsa.pub') 68 | priv_path = join(ssh_path, 'id_rsa') 69 | 70 | host = self.execute_python('import os; print os.uname()[1]', stdout=False) 71 | host_str = "%s@%s" % (user, host) 72 | 73 | pub_text = "%s %s" % (public_key, host_str) 74 | pub_file = self.write_to_temp_file(pub_text) 75 | priv_file = self.write_to_temp_file(private_key) 76 | result_pub = self.update_file(pub_file, pub_path, sudo=True, owner=user) 77 | result_priv = self.update_file(priv_file, priv_path, sudo=True, owner=user) 78 | 79 | if result_pub or result_priv: 80 | self.log("SSH keys generated at server!") 81 | self.log("Public key:") 82 | self.log(pub_text) 83 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: images/logo.png 2 | 3 | ===== 4 | provy 5 | ===== 6 | **Python** provisioning made **easy**! 7 | 8 | .. toctree:: 9 | :maxdepth: 4 10 | :hidden: 11 | 12 | /whats-provy 13 | /changelog 14 | /installing 15 | /getting-started 16 | /provyfile 17 | /running 18 | /what-are-roles 19 | /using-roles 20 | /custom-files 21 | /roles-docs 22 | /supported-os 23 | /recipes 24 | /whos-using 25 | /ideas 26 | /contributing 27 | 28 | About 29 | ===== 30 | 31 | **provy** is an easy-to-use provisioning system in python. 32 | 33 | Turn that tedious task of provisioning the infrastructure of your website into a repeatable no-frills reliable process. 34 | 35 | Documentation 36 | ============= 37 | 38 | (:doc:`Looking for the API? Here's a shortcut! `) 39 | 40 | * :doc:`whats-provy` 41 | * :doc:`changelog` 42 | * :doc:`installing` 43 | * :doc:`getting-started` 44 | * :doc:`provyfile` 45 | * :doc:`running` 46 | * :doc:`what-are-roles` 47 | * :doc:`using-roles` 48 | * :doc:`custom-files` 49 | * :doc:`roles-docs` 50 | * :doc:`supported-os` 51 | * :doc:`recipes` 52 | * :doc:`whos-using` 53 | * :doc:`ideas` 54 | * :doc:`contributing` 55 | 56 | Contacts 57 | ======== 58 | 59 | The place to create issues is `provy's github issues `_. The more information you send about an issue, the greater the chance it will get fixed fast. 60 | 61 | If you are not sure about something, have a doubt or feedback, or just want to ask for a feature, feel free to join `our mailing list `_, or, if you're on FreeNode (IRC), you can join the chat #provy . 62 | 63 | License 64 | ======= 65 | 66 | *provy* is licensed under the `MIT License `_ 67 | 68 | Copyright (c) 2011 Bernardo Heynemann 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy of 71 | this software and associated documentation files (the "Software"), to deal in 72 | the Software without restriction, including without limitation the rights to use, 73 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 74 | Software, and to permit persons to whom the Software is furnished to do so, 75 | subject to the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be included in all 78 | copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 81 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 82 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 83 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 84 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 85 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 86 | 87 | 88 | Indices and tables 89 | ================== 90 | 91 | * :ref:`genindex` 92 | * :ref:`modindex` 93 | * :ref:`search` 94 | 95 | -------------------------------------------------------------------------------- /tests/unit/more/centos/utils/test_hostname.py: -------------------------------------------------------------------------------- 1 | from mock import patch, call 2 | from nose.tools import istest 3 | 4 | from provy.more.centos import HostNameRole 5 | from provy.more.centos.utils import hostname 6 | from tests.unit.tools.helpers import ProvyTestCase 7 | 8 | 9 | class HostNameRoleTest(ProvyTestCase): 10 | def setUp(self): 11 | super(HostNameRoleTest, self).setUp() 12 | self.role = HostNameRole(None, {}) 13 | 14 | @istest 15 | def ensures_a_hostname_is_configured_when_not_existing(self): 16 | new_hostname = 'new-hostname' 17 | with self.mock_role_methods('read_remote_file', 'execute', 'ensure_line'), patch.object(hostname, 'sed') as sed: 18 | self.role.execute.return_value = 'previous-hostname' 19 | self.role.read_remote_file.return_value = ''' 20 | some config 21 | HOSTNAME={} 22 | some other config 23 | '''.format(new_hostname) 24 | 25 | result = self.role.ensure_hostname(new_hostname) 26 | 27 | self.assertTrue(result) 28 | self.role.read_remote_file.assert_called_once_with('/etc/sysconfig/network') 29 | self.assertEqual(self.role.execute.mock_calls, [ 30 | call('hostname'), 31 | call('hostname "{}"'.format(new_hostname), sudo=True, stdout=False), 32 | ]) 33 | self.assertFalse(self.role.ensure_line.called) 34 | sed.assert_called_once_with('/etc/sysconfig/network', 'HOSTNAME=.*', 'HOSTNAME=new-hostname', use_sudo=True) 35 | 36 | @istest 37 | def ensures_a_hostname_is_configured_when_another_one_already_exists(self): 38 | new_hostname = 'new-hostname' 39 | with self.mock_role_methods('read_remote_file', 'execute', 'ensure_line'), patch.object(hostname, 'sed') as sed: 40 | self.role.execute.return_value = 'previous-hostname' 41 | self.role.read_remote_file.return_value = ''' 42 | some config 43 | some other config 44 | '''.format(new_hostname) 45 | 46 | result = self.role.ensure_hostname(new_hostname) 47 | 48 | self.assertTrue(result) 49 | self.role.read_remote_file.assert_called_once_with('/etc/sysconfig/network') 50 | self.assertEqual(self.role.execute.mock_calls, [ 51 | call('hostname'), 52 | call('hostname "{}"'.format(new_hostname), sudo=True, stdout=False), 53 | ]) 54 | self.assertFalse(sed.called) 55 | self.role.ensure_line.assert_called_once_with('HOSTNAME={}'.format(new_hostname), sudo=True, stdout=False) 56 | 57 | @istest 58 | def doesnt_configure_the_hostname_if_same_as_server(self): 59 | new_hostname = 'new-hostname' 60 | with self.mock_role_methods('execute'): 61 | self.role.execute.return_value = new_hostname 62 | 63 | result = self.role.ensure_hostname(new_hostname) 64 | 65 | self.assertFalse(result) 66 | self.assertEqual(self.role.execute.mock_calls, [ 67 | call('hostname'), 68 | ]) 69 | -------------------------------------------------------------------------------- /provy/more/centos/database/postgresql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `PostgreSQL `_ database management utilities for CentOS distributions. 6 | ''' 7 | import re 8 | 9 | import fabric 10 | 11 | from provy.more.base.database import BasePostgreSQLRole 12 | from provy.more.centos.package.yum import YumRole 13 | 14 | 15 | class PostgreSQLRole(BasePostgreSQLRole): 16 | ''' 17 | This role provides `PostgreSQL `_ database management utilities for CentOS distributions. 18 | 19 | Take a look at :class:`provy.more.base.database.postgresql.BasePostgreSQLRole` for more available methods. 20 | 21 | Example: 22 | :: 23 | 24 | from provy.core import Role 25 | from provy.more.centos import PostgreSQLRole 26 | 27 | class MySampleRole(Role): 28 | def provision(self): 29 | with self.using(PostgreSQLRole) as role: 30 | role.ensure_user("john") 31 | role.ensure_database("foo", owner="john") 32 | ''' 33 | def provision(self): 34 | ''' 35 | Installs `PostgreSQL `_ and its dependencies. 36 | This method should be called upon if overriden in base classes, or PostgreSQL won't work properly in the remote server. 37 | 38 | Example: 39 | :: 40 | 41 | class MySampleRole(Role): 42 | def provision(self): 43 | self.provision_role(PostgreSQLRole) # no need to call this if using with block. 44 | ''' 45 | with self.using(YumRole) as role: 46 | role.ensure_package_installed('postgresql-server') 47 | role.ensure_package_installed('postgresql-devel') 48 | 49 | self._ensure_initialized() 50 | self._ensure_running() 51 | self._run_on_startup() 52 | 53 | def _execute(self, *args, **kwargs): 54 | with fabric.api.cd('/var/lib/pgsql'): 55 | return super(PostgreSQLRole, self)._execute(*args, **kwargs) 56 | 57 | def _is_db_initialized(self): 58 | pgdata = '/var/lib/pgsql/data' 59 | return self.execute('ls -A %s' % pgdata, sudo=True, stdout=False) 60 | 61 | def _ensure_initialized(self): 62 | if not self._is_db_initialized(): 63 | return(self.execute("service postgresql initdb", sudo=True)) 64 | return True 65 | 66 | def _is_running(self): 67 | with fabric.api.settings(warn_only=True): 68 | status = self.execute('service postgresql status', sudo=True, stdout=False) 69 | return 'running' in status 70 | 71 | def _ensure_running(self): 72 | if not self._is_running(): 73 | return self.execute('service postgresql start', sudo=True) 74 | return True 75 | 76 | def _will_start_on_boot(self): 77 | pkg_list = self.execute('chkconfig --list', sudo=True, stdout=False) 78 | return re.search(r'postgresql.*\t0:off\t1:off\t2:on\t3:on\t4:on\t5:on\t6:off', pkg_list) 79 | 80 | def _run_on_startup(self): 81 | if not self._will_start_on_boot(): 82 | self.execute('chkconfig --add postgresql', sudo=True) 83 | self.execute('chkconfig postgresql on', sudo=True) 84 | return True 85 | return False 86 | -------------------------------------------------------------------------------- /tests/unit/tools/helpers.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from os.path import abspath, dirname, join 3 | from unittest import TestCase 4 | 5 | from mock import MagicMock, patch, DEFAULT 6 | 7 | from provy.core.roles import DistroInfo, Role 8 | 9 | 10 | PROJECT_ROOT = abspath(join(dirname(__file__), '..', '..', '..')) 11 | 12 | 13 | class ProvyTestCase(TestCase): 14 | def setUp(self): 15 | self.role = Role(prov=None, context={}) 16 | self.using_mocks = {} 17 | 18 | @contextmanager 19 | def using_stub(self, role): 20 | mock_role = MagicMock(spec=role) 21 | self.using_mocks[role] = mock_role 22 | self.role.context.setdefault('roles_in_context', {}) 23 | 24 | @contextmanager 25 | def stub_using(inner_self, klass): 26 | role_instance = self.using_mocks[klass] 27 | self.role.context['roles_in_context'][klass] = role_instance 28 | yield role_instance 29 | del self.role.context['roles_in_context'][klass] 30 | 31 | with patch('provy.core.roles.Role.using', stub_using): 32 | yield mock_role 33 | 34 | @contextmanager 35 | def execute_mock(self): 36 | with patch('provy.core.roles.Role.execute') as execute: 37 | yield execute 38 | 39 | @contextmanager 40 | def mock_role_method(self, method): 41 | ''' 42 | Mocks a method in the current role instance's class - i.e., not necessarily provy.core.roles.Role, depends on the object that self.role holds. 43 | ''' 44 | with patch.object(self.role.__class__, method) as mock: 45 | yield mock 46 | 47 | @contextmanager 48 | def mock_role_methods(self, *methods): 49 | ''' 50 | Same as mock_role_method, except that several methods can be provided. 51 | ''' 52 | methods_to_mock = dict((method, DEFAULT) for method in methods) 53 | with patch.multiple(self.role.__class__, **methods_to_mock) as mocks: 54 | yield tuple(mocks[method] for method in methods) 55 | 56 | def debian_info(self): 57 | distro_info = DistroInfo() 58 | distro_info.distributor_id = 'Debian' 59 | distro_info.description = 'Debian GNU/Linux 6.0.5 (squeeze)' 60 | distro_info.release = '6.0.5' 61 | distro_info.codename = 'squeeze' 62 | return distro_info 63 | 64 | def ubuntu_info(self): 65 | distro_info = DistroInfo() 66 | distro_info.distributor_id = 'Ubuntu' 67 | distro_info.description = 'Ubuntu 12.04.1 LTS' 68 | distro_info.release = '12.04' 69 | distro_info.codename = 'precise' 70 | return distro_info 71 | 72 | @contextmanager 73 | def provisioning_to(self, distro): 74 | with self.mock_role_method('get_distro_info') as get_distro_info: 75 | if distro == 'ubuntu': 76 | distro_info = self.ubuntu_info() 77 | else: 78 | distro_info = self.debian_info() 79 | get_distro_info.return_value = distro_info 80 | yield 81 | 82 | @contextmanager 83 | def warn_only(self): 84 | test_case = self 85 | 86 | @contextmanager 87 | def settings(warn_only): 88 | test_case.assertTrue(warn_only) 89 | yield 90 | 91 | with patch('fabric.api.settings', settings): 92 | yield 93 | -------------------------------------------------------------------------------- /tests/unit/core/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from mock import patch 4 | from nose.tools import istest 5 | 6 | from provy.core.utils import provyfile_path_from, provyfile_module_from, import_module 7 | from tests.unit.tools.helpers import ProvyTestCase 8 | 9 | 10 | class UtilsTest(ProvyTestCase): 11 | @istest 12 | def gets_provyfile_path_from_args(self): 13 | existing_file = 'path/to/provyfile.py' 14 | 15 | with patch.object(os.path, 'exists') as exists: 16 | exists.return_value = True 17 | 18 | self.assertEqual(provyfile_path_from(args=[existing_file]), existing_file) 19 | 20 | @istest 21 | def raises_exception_if_file_given_but_not_existant(self): 22 | existing_file = 'path/to/provyfile.py' 23 | 24 | with patch.object(os.path, 'exists') as exists: 25 | exists.return_value = False 26 | 27 | self.assertRaises(IOError, provyfile_path_from, args=[existing_file]) 28 | 29 | @istest 30 | def raises_exception_if_file_given_is_absolute(self): 31 | existing_file = '/path/to/provyfile.py' 32 | 33 | with patch.object(os.path, 'exists') as exists: 34 | exists.return_value = True 35 | 36 | self.assertRaises(ValueError, provyfile_path_from, args=[existing_file]) 37 | 38 | @istest 39 | def gets_provyfile_as_default_value_if_existant(self): 40 | with patch.object(os.path, 'exists') as exists: 41 | exists.side_effect = [True] 42 | 43 | self.assertEqual(provyfile_path_from(args=[]), 'provyfile.py') 44 | 45 | @istest 46 | def gets_provy_file_as_default_value_if_existant(self): 47 | with patch.object(os.path, 'exists') as exists: 48 | exists.side_effect = [False, True] 49 | 50 | self.assertEqual(provyfile_path_from(args=[]), 'provy_file.py') 51 | 52 | @istest 53 | def raises_exception_if_no_provyfile_is_found(self): 54 | with patch.object(os.path, 'exists') as exists: 55 | exists.side_effect = [False, False] 56 | 57 | self.assertRaises(IOError, provyfile_path_from, args=[]) 58 | 59 | @istest 60 | def gets_provyfile_module_from_simple_path(self): 61 | self.assertEqual(provyfile_module_from('provyfile.py'), 'provyfile') 62 | 63 | @istest 64 | def gets_provyfile_module_from_nested_path(self): 65 | self.assertEqual(provyfile_module_from('some/dir/provyfile.py'), 'some.dir.provyfile') 66 | 67 | @istest 68 | def gets_provyfile_module_from_nested_path_without_extenstion(self): 69 | self.assertEqual(provyfile_module_from('some/dir/provyfile'), 'some.dir.provyfile') 70 | 71 | @istest 72 | def imports_a_module_with_dotted_notation(self): 73 | class foo_package: 74 | class bar_package: 75 | class baz_module: 76 | pass 77 | 78 | with patch('__builtin__.__import__') as import_: 79 | import_.return_value = foo_package 80 | 81 | module = import_module('foo_package.bar_package.baz_module') 82 | 83 | self.assertEqual(module, foo_package.bar_package.baz_module) 84 | 85 | @istest 86 | def imports_a_module_with_simple_notation(self): 87 | class foo_module: 88 | pass 89 | 90 | with patch('__builtin__.__import__') as import_: 91 | import_.return_value = foo_module 92 | 93 | module = import_module('foo_module') 94 | 95 | self.assertEqual(module, foo_module) 96 | -------------------------------------------------------------------------------- /tests/unit/more/debian/security/test_ufw.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.debian import AptitudeRole, UFWRole 4 | from tests.unit.tools.helpers import ProvyTestCase 5 | 6 | 7 | class UFWRoleTest(ProvyTestCase): 8 | def setUp(self): 9 | super(UFWRoleTest, self).setUp() 10 | self.role = UFWRole(prov=None, context={'cleanup': []}) 11 | 12 | @istest 13 | def installs_necessary_packages_to_provision(self): 14 | with self.using_stub(AptitudeRole) as aptitude, self.execute_mock(): 15 | self.role.provision() 16 | 17 | aptitude.ensure_package_installed.assert_any_call('ufw') 18 | 19 | @istest 20 | def allows_ssh_connection_during_provisioning(self): 21 | with self.using_stub(AptitudeRole), self.execute_mock() as execute: 22 | self.role.provision() 23 | 24 | execute.assert_any_call('ufw allow ssh', stdout=False, sudo=True) 25 | 26 | @istest 27 | def enables_when_finishing_provisioning(self): 28 | with self.execute_mock() as execute: 29 | self.role.schedule_cleanup() 30 | 31 | execute.assert_any_call("ufw --force enable", stdout=False, sudo=True) 32 | 33 | @istest 34 | def allows_a_certain_port_by_application_name(self): 35 | with self.execute_mock() as execute: 36 | self.role.allow('http') 37 | 38 | execute.assert_called_with('ufw allow http', stdout=False, sudo=True) 39 | 40 | @istest 41 | def allows_a_certain_port_by_number(self): 42 | with self.execute_mock() as execute: 43 | self.role.allow(8000) 44 | 45 | execute.assert_called_with('ufw allow 8000', stdout=False, sudo=True) 46 | 47 | @istest 48 | def allows_a_certain_port_by_number_and_protocol(self): 49 | with self.execute_mock() as execute: 50 | self.role.allow(8000, protocol='tcp') 51 | 52 | execute.assert_called_with('ufw allow 8000/tcp', stdout=False, sudo=True) 53 | 54 | @istest 55 | def allows_a_certain_port_by_number_and_direction(self): 56 | with self.execute_mock() as execute: 57 | self.role.allow(8000, direction='in') 58 | 59 | execute.assert_called_with('ufw allow in 8000', stdout=False, sudo=True) 60 | 61 | @istest 62 | def allows_a_certain_port_by_number_and_protocol_and_direction(self): 63 | with self.execute_mock() as execute: 64 | self.role.allow(8000, protocol='tcp', direction='in') 65 | 66 | execute.assert_called_with('ufw allow in 8000/tcp', stdout=False, sudo=True) 67 | 68 | @istest 69 | def drops_a_certain_port_by_number_and_protocol_and_direction(self): 70 | with self.execute_mock() as execute: 71 | self.role.drop(8000, protocol='tcp', direction='in') 72 | 73 | execute.assert_called_with('ufw deny in 8000/tcp', stdout=False, sudo=True) 74 | 75 | @istest 76 | def rejects_a_certain_port_by_number_and_protocol_and_direction(self): 77 | with self.execute_mock() as execute: 78 | self.role.reject(8000, protocol='tcp', direction='in') 79 | 80 | execute.assert_called_with('ufw reject in 8000/tcp', stdout=False, sudo=True) 81 | 82 | @istest 83 | def allows_with_a_custom_query(self): 84 | with self.execute_mock() as execute: 85 | self.role.allow('proto tcp to any port 80') 86 | 87 | execute.assert_called_with('ufw allow proto tcp to any port 80', stdout=False, sudo=True) 88 | -------------------------------------------------------------------------------- /tests/unit/more/centos/database/test_postgresql.py: -------------------------------------------------------------------------------- 1 | from mock import call, patch 2 | from nose.tools import istest 3 | 4 | from provy.more.centos import YumRole, PostgreSQLRole 5 | from tests.unit.more.base.database import test_postgresql 6 | 7 | 8 | class PostgreSQLRoleTestCase(test_postgresql.PostgreSQLRoleTestCase): 9 | def setUp(self): 10 | super(PostgreSQLRoleTestCase, self).setUp() 11 | self.role = PostgreSQLRole(prov=None, context={}) 12 | 13 | 14 | class PostgreSQLRoleTest(PostgreSQLRoleTestCase): 15 | @istest 16 | def ensure_initialized(self): 17 | with self.failed_execution('ls -A /var/lib/pgsql/data', None): 18 | with self.successful_execution('service postgresql initdb', None): 19 | self.assertTrue(self.role._ensure_initialized()) 20 | 21 | with self.successful_execution('ls -A /var/lib/pgsql/data', None): 22 | self.assertTrue(self.role._ensure_initialized()) 23 | 24 | @istest 25 | def verifies_db_is_initialized(self): 26 | with self.successful_execution('ls -A /var/lib/pgsql/data', None): 27 | self.assertTrue(self.role._is_db_initialized()) 28 | 29 | @istest 30 | def verifies_db_is_not_initialized(self): 31 | with self.failed_execution('ls -A /var/lib/pgsql/data', None): 32 | self.assertFalse(self.role._is_db_initialized()) 33 | 34 | @istest 35 | def ensures_postegres_is_running(self): 36 | with self.execution('..stopped..', 'service postgresql status', None): 37 | with self.successful_execution('service postgresql start', None): 38 | self.assertTrue(self.role._ensure_running()) 39 | 40 | @istest 41 | def ensures_postegres_is_not_running(self): 42 | with self.execution('..running..', 'service postgresql status', None): 43 | self.assertTrue(self.role._ensure_running()) 44 | 45 | @istest 46 | def ensures_postgres_on_startup(self): 47 | with self.execution('......', 'chkconfig --list', None): 48 | with self.execution('', 'chkconfig --add postgresql', None): 49 | with self.execution('', 'chkconfig postgresql on', None): 50 | self.assertTrue(self.role._run_on_startup()) 51 | 52 | @istest 53 | def ensures_postgres_not_on_startup(self): 54 | with self.execution('..\r\npostgresql \t0:off\t1:off\t2:on\t3:on\t4:on\t5:on\t6:off\r\n..', 'chkconfig --list', None): 55 | self.assertFalse(self.role._run_on_startup()) 56 | 57 | @istest 58 | def change_directory_to_postgres_data_dir(self): 59 | with patch('fabric.api.cd') as cd_mock, self.execute_mock() as execute: 60 | self.role._execute('ls') 61 | self.assertEqual(cd_mock.call_args, call('/var/lib/pgsql')) 62 | self.assertEqual( 63 | execute.call_args, call('ls', sudo=True, stdout=True, user='postgres') 64 | ) 65 | 66 | @istest 67 | def installs_necessary_packages_to_provision(self): 68 | with self.using_stub(YumRole) as mock_yum, self.mock_role_methods('_run_on_startup', '_ensure_running', '_ensure_initialized'): 69 | print mock_yum 70 | self.role.provision() 71 | install_calls = mock_yum.ensure_package_installed.mock_calls 72 | self.assertEqual( 73 | install_calls, 74 | [call('postgresql-server'), call('postgresql-devel')], 75 | ) 76 | 77 | self.assertTrue(self.role._run_on_startup.called) 78 | self.assertTrue(self.role._ensure_running.called) 79 | self.assertTrue(self.role._ensure_initialized.called) 80 | -------------------------------------------------------------------------------- /provy/more/debian/programming/ruby.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `Ruby `_ utility methods for Debian distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.debian import AptitudeRole 10 | 11 | 12 | UPDATE_ALTERNATIVES_COMMAND = """ 13 | update-alternatives --force --install /usr/bin/ruby ruby /usr/bin/ruby{version} {priority} \ 14 | --slave /usr/share/man/man1/ruby.1.gz ruby.1.gz /usr/share/man/man1/ruby{version}.1.gz \ 15 | --slave /usr/bin/ri ri /usr/bin/ri{version} \ 16 | --slave /usr/share/man/man1/ri.1.gz ri.1.gz /usr/share/man/man1/ri{version}.1.gz \ 17 | --slave /usr/bin/irb irb /usr/bin/irb{version} \ 18 | --slave /usr/share/man/man1/irb.1.gz irb.1.gz /usr/share/man/man1/irb{version}.1.gz \ 19 | --slave /usr/bin/erb erb /usr/bin/erb{version} \ 20 | --slave /usr/share/man/man1/erb.1.gz erb.1.gz /usr/share/man/man1/erb{version}.1.gz \ 21 | --slave /usr/bin/rdoc rdoc /usr/bin/rdoc{version} \ 22 | --slave /usr/share/man/man1/rdoc.1.gz rdoc.1.gz /usr/share/man/man1/rdoc{version}.1.gz \ 23 | --slave /usr/bin/testrb testrb /usr/bin/testrb{version} \ 24 | --slave /usr/share/man/man1/testrb.1.gz testrb.1.gz /usr/share/man/man1/testrb{version}.1.gz 25 | """ 26 | 27 | 28 | class RubyRole(Role): 29 | ''' 30 | This role provides `Ruby `_ utilities for Debian distributions. 31 | 32 | :var version: Ruby version to install. By default, install package "1.9.1" - which, in effect, refers to "1.9.2" (only uses the "1.9.1" name for compatibility reasons). 33 | :type version: :class:`str` 34 | :var priority: Priority to attribute to this Ruby version in the server. By default, it's 400 - which is already higher than the default Ruby installation in some Debian-like systems -. 35 | :type priority: :class:`int` 36 | 37 | Example: 38 | :: 39 | 40 | from provy.core import Role 41 | from provy.more.debian import RubyRole 42 | 43 | class MySampleRole(Role): 44 | def provision(self): 45 | self.provision_role(RubyRole) 46 | 47 | # Now, suppose we want the new Ruby installed, but not as the default one: 48 | RubyRole.version = 1.8 49 | RubyRole.priority = 10 50 | self.provision_role(RubyRole) 51 | RubyRole.version = 1.9.1 52 | RubyRole.priority = 1 53 | self.provision_role(RubyRole) 54 | # As priority 10 wins over 1, Ruby 1.8 will be used as the default "ruby" executable. 55 | ''' 56 | 57 | version = '1.9.1' 58 | priority = 400 59 | 60 | def provision(self): 61 | ''' 62 | Installs `Ruby `_ and its dependencies. 63 | This method should be called upon if overriden in base classes, or Ruby won't work properly in the remote server. 64 | 65 | Example: 66 | :: 67 | 68 | from provy.core import Role 69 | from provy.more.debian import RubyRole 70 | 71 | class MySampleRole(Role): 72 | def provision(self): 73 | self.provision_role(RubyRole) # no need to call this if using with block. 74 | ''' 75 | with self.using(AptitudeRole) as aptitude: 76 | aptitude.ensure_up_to_date() 77 | aptitude.ensure_package_installed('ruby{version}-full'.format(version=self.version)) 78 | 79 | update_alternatives_command = UPDATE_ALTERNATIVES_COMMAND.format( 80 | version=self.version, 81 | priority=self.priority, 82 | ) 83 | self.execute(update_alternatives_command, sudo=True) 84 | -------------------------------------------------------------------------------- /tests/unit/more/debian/package/test_gem.py: -------------------------------------------------------------------------------- 1 | from mock import call 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import GemRole, RubyRole 5 | from provy.more.debian.package.gem import UPDATE_ALTERNATIVES_COMMAND 6 | from tests.unit.tools.helpers import ProvyTestCase 7 | 8 | 9 | class GemRoleTest(ProvyTestCase): 10 | def setUp(self): 11 | super(GemRoleTest, self).setUp() 12 | self.role = GemRole(prov=None, context={}) 13 | 14 | @istest 15 | def installs_necessary_packages_to_provision(self): 16 | with self.mock_role_method('provision_role'), self.execute_mock() as execute: 17 | self.role.provision() 18 | 19 | update_alternatives_command = UPDATE_ALTERNATIVES_COMMAND.format( 20 | version=RubyRole.version, 21 | priority=RubyRole.priority, 22 | ) 23 | completion_command = 'ln - sf /etc/bash_completion.d/gem{version} /etc/alternatives/bash_completion_gem'.format(version=RubyRole.version) 24 | 25 | self.role.provision_role.assert_called_once_with(RubyRole) 26 | self.assertEqual(execute.mock_calls, [ 27 | call(update_alternatives_command, sudo=True), 28 | call(completion_command, sudo=True), 29 | ]) 30 | 31 | @istest 32 | def checks_that_a_package_is_installed(self): 33 | with self.execute_mock() as execute: 34 | execute.return_value = 'some foo is installed' 35 | 36 | result = self.role.is_package_installed('foo') 37 | 38 | self.assertTrue(result) 39 | execute.assert_called_once_with("gem list --local | tr '[A-Z]' '[a-z]' | grep foo", stdout=False, sudo=True) 40 | 41 | @istest 42 | def checks_that_a_package_is_not_installed(self): 43 | with self.execute_mock() as execute: 44 | execute.return_value = 'some bar is installed' 45 | 46 | result = self.role.is_package_installed('foo') 47 | 48 | self.assertFalse(result) 49 | 50 | @istest 51 | def checks_that_a_package_is_installed_with_version(self): 52 | with self.execute_mock() as execute: 53 | execute.return_value = 'some foo is installed' 54 | 55 | result = self.role.is_package_installed('foo', '1.8') 56 | 57 | self.assertTrue(result) 58 | execute.assert_called_once_with("gem list --local | tr '[A-Z]' '[a-z]' | grep foo(1.8)", stdout=False, sudo=True) 59 | 60 | @istest 61 | def installs_a_package_if_its_not_installed_yet_by_name(self): 62 | with self.execute_mock() as execute, self.mock_role_method('is_package_installed') as is_package_installed: 63 | is_package_installed.return_value = False 64 | 65 | result = self.role.ensure_package_installed('runit') 66 | 67 | self.assertTrue(result) 68 | execute.assert_called_with('gem install runit', stdout=False, sudo=True) 69 | 70 | @istest 71 | def doesnt_install_a_package_if_its_already_installed_yet_by_name(self): 72 | with self.execute_mock() as execute, self.mock_role_method('is_package_installed') as is_package_installed: 73 | is_package_installed.return_value = True 74 | 75 | result = self.role.ensure_package_installed('runit') 76 | 77 | self.assertFalse(result) 78 | self.assertFalse(execute.called) 79 | 80 | @istest 81 | def installs_a_package_if_its_not_installed_yet_by_name_and_version(self): 82 | with self.execute_mock() as execute, self.mock_role_method('is_package_installed') as is_package_installed: 83 | is_package_installed.return_value = False 84 | 85 | self.role.ensure_package_installed('runit', '123') 86 | 87 | execute.assert_called_with('gem install runit(123)', sudo=True, stdout=False) 88 | -------------------------------------------------------------------------------- /tests/unit/more/debian/security/test_apparmor.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.debian import AptitudeRole, AppArmorRole 4 | from tests.unit.tools.helpers import ProvyTestCase 5 | 6 | 7 | class AppArmorRoleTest(ProvyTestCase): 8 | def setUp(self): 9 | super(AppArmorRoleTest, self).setUp() 10 | self.role = AppArmorRole(prov=None, context={'cleanup': []}) 11 | 12 | @istest 13 | def installs_necessary_packages_to_provision(self): 14 | with self.using_stub(AptitudeRole) as aptitude, self.execute_mock(): 15 | self.role.provision() 16 | 17 | aptitude.ensure_package_installed.assert_any_call('apparmor-profiles') 18 | aptitude.ensure_package_installed.assert_any_call('apparmor-utils') 19 | 20 | @istest 21 | def disables_executables(self): 22 | with self.execute_mock() as execute: 23 | self.role.disable('/some/bin1', '/some/bin2') 24 | 25 | execute.assert_called_with('aa-disable /some/bin1 /some/bin2', stdout=False, sudo=True) 26 | 27 | @istest 28 | def puts_executables_to_complain_mode(self): 29 | with self.execute_mock() as execute: 30 | self.role.complain('/some/bin1', '/some/bin2') 31 | 32 | execute.assert_called_with('aa-complain /some/bin1 /some/bin2', stdout=False, sudo=True) 33 | 34 | @istest 35 | def puts_executables_to_enforce_mode(self): 36 | with self.execute_mock() as execute: 37 | self.role.enforce('/some/bin1', '/some/bin2') 38 | 39 | execute.assert_called_with('aa-enforce /some/bin1 /some/bin2', stdout=False, sudo=True) 40 | 41 | @istest 42 | def puts_executables_to_audit_mode(self): 43 | with self.execute_mock() as execute: 44 | self.role.audit('/some/bin1', '/some/bin2') 45 | 46 | execute.assert_called_with('aa-audit /some/bin1 /some/bin2', stdout=False, sudo=True) 47 | 48 | @istest 49 | def creates_a_profile_for_an_executable(self): 50 | with self.execute_mock() as execute: 51 | self.role.create('/some/bin') 52 | 53 | execute.assert_called_with('aa-easyprof /some/bin', stdout=False, sudo=True) 54 | 55 | @istest 56 | def creates_a_profile_with_another_template(self): 57 | with self.execute_mock() as execute: 58 | self.role.create('/some/bin', template='another-template') 59 | 60 | execute.assert_called_with('aa-easyprof -t another-template /some/bin', stdout=False, sudo=True) 61 | 62 | @istest 63 | def creates_a_profile_with_policy_groups(self): 64 | with self.execute_mock() as execute: 65 | self.role.create('/some/bin', policy_groups=['networking', 'user-application']) 66 | 67 | execute.assert_called_with('aa-easyprof -p networking,user-application /some/bin', stdout=False, sudo=True) 68 | 69 | @istest 70 | def creates_a_profile_with_abstractions(self): 71 | with self.execute_mock() as execute: 72 | self.role.create('/some/bin', abstractions=['python', 'apache2-common']) 73 | 74 | execute.assert_called_with('aa-easyprof -a python,apache2-common /some/bin', stdout=False, sudo=True) 75 | 76 | @istest 77 | def creates_a_profile_with_read_permissions(self): 78 | with self.execute_mock() as execute: 79 | self.role.create('/some/bin', read=['/var/log/somebin.log', '/srv/somebin/']) 80 | 81 | execute.assert_called_with('aa-easyprof -r /var/log/somebin.log -r /srv/somebin/ /some/bin', stdout=False, sudo=True) 82 | 83 | @istest 84 | def creates_a_profile_with_read_and_write_permissions(self): 85 | with self.execute_mock() as execute: 86 | self.role.create('/some/bin', read_and_write=['/var/log/somebin.log', '/srv/somebin/']) 87 | 88 | execute.assert_called_with('aa-easyprof -w /var/log/somebin.log -w /srv/somebin/ /some/bin', stdout=False, sudo=True) 89 | -------------------------------------------------------------------------------- /tests/unit/more/debian/web/test_apache.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import ApacheRole, AptitudeRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class ApacheRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(ApacheRoleTest, self).setUp() 11 | self.role = ApacheRole(prov=None, context={}) 12 | 13 | @istest 14 | def installs_necessary_packages_to_provision(self): 15 | with self.using_stub(AptitudeRole) as aptitude: 16 | self.role.provision() 17 | 18 | aptitude.ensure_package_installed.assert_called_with('apache2') 19 | 20 | @istest 21 | def ensures_module_is_installed_and_enabled(self): 22 | with self.using_stub(AptitudeRole) as aptitude, self.execute_mock() as execute: 23 | self.role.ensure_mod('foo') 24 | 25 | aptitude.ensure_package_installed.assert_called_with('libapache2-mod-foo') 26 | execute.assert_called_with('a2enmod foo', sudo=True) 27 | self.assertTrue(self.role.must_restart) 28 | 29 | @istest 30 | def ensures_site_is_available_from_template(self): 31 | with self.execute_mock(), self.mock_role_method('update_file') as update_file, self.mock_role_method('remote_symlink'): 32 | self.role.create_site('bar-website', template='/local/path/to/bar-website') 33 | 34 | update_file.assert_called_with('/local/path/to/bar-website', '/etc/apache2/sites-available/bar-website', options={}, sudo=True) 35 | self.assertTrue(self.role.must_restart) 36 | 37 | @istest 38 | def ensures_site_is_available_from_template_and_options(self): 39 | with self.execute_mock(), self.mock_role_method('update_file') as update_file, self.mock_role_method('remote_symlink'): 40 | self.role.create_site('bar-website', template='/local/path/to/bar-website', options={'foo': 'Baz'}) 41 | 42 | update_file.assert_called_with('/local/path/to/bar-website', '/etc/apache2/sites-available/bar-website', options={'foo': 'Baz'}, sudo=True) 43 | self.assertTrue(self.role.must_restart) 44 | 45 | @istest 46 | def ensures_that_a_website_is_enabled(self): 47 | with self.mock_role_method('remote_symlink') as remote_symlink: 48 | self.role.ensure_site_enabled('bar-website') 49 | 50 | remote_symlink.assert_called_with(from_file='/etc/apache2/sites-available/bar-website', to_file='/etc/apache2/sites-enabled/bar-website', sudo=True) 51 | self.assertTrue(self.role.must_restart) 52 | 53 | @istest 54 | def ensures_that_a_website_is_disabled(self): 55 | with self.mock_role_method('remove_file') as remove_file: 56 | self.role.ensure_site_disabled('bar-website') 57 | 58 | remove_file.assert_called_with('/etc/apache2/sites-enabled/bar-website', sudo=True) 59 | self.assertTrue(self.role.must_restart) 60 | 61 | @istest 62 | def can_be_restarted(self): 63 | with self.execute_mock() as execute: 64 | self.role.restart() 65 | 66 | execute.assert_called_with('service apache2 restart', sudo=True) 67 | 68 | @istest 69 | def ensures_that_it_must_be_restarted(self): 70 | 71 | self.assertFalse(self.role.must_restart) 72 | 73 | self.role.ensure_restart() 74 | 75 | self.assertTrue(self.role.must_restart) 76 | 77 | @istest 78 | def must_not_restart_again_if_already_restarted(self): 79 | with self.execute_mock(): 80 | self.role.ensure_restart() 81 | self.role.restart() 82 | 83 | self.assertFalse(self.role.must_restart) 84 | 85 | @istest 86 | def restarts_on_cleanup_if_must_be_restarted(self): 87 | with patch('provy.more.debian.ApacheRole.restart') as restart: 88 | self.role.ensure_restart() 89 | self.role.cleanup() 90 | 91 | self.assertTrue(restart.called) 92 | 93 | @istest 94 | def doesnt_restart_on_cleanup_if_doesnt_need_to_be_restarted(self): 95 | with patch('provy.more.debian.ApacheRole.restart') as restart: 96 | self.role.cleanup() 97 | 98 | self.assertFalse(restart.called) 99 | -------------------------------------------------------------------------------- /tests/unit/more/debian/cache/test_varnish.py: -------------------------------------------------------------------------------- 1 | from nose.tools import istest 2 | 3 | from provy.more.debian import AptitudeRole, VarnishRole 4 | from tests.unit.tools.helpers import ProvyTestCase 5 | 6 | 7 | class VarnishRoleTest(ProvyTestCase): 8 | def setUp(self): 9 | super(VarnishRoleTest, self).setUp() 10 | self.role = VarnishRole(prov=None, context={'owner': 'some-owner'}) 11 | 12 | @istest 13 | def installs_necessary_packages_to_provision(self): 14 | with self.using_stub(AptitudeRole) as aptitude: 15 | self.role.provision() 16 | 17 | aptitude.ensure_package_installed.assert_called_once_with('varnish') 18 | 19 | @istest 20 | def updates_vcl_and_restarts(self): 21 | template = 'some-template' 22 | varnish_vcl_path = 'some-conf-path' 23 | options = {'foo': 'bar'} 24 | owner = 'some-owner' 25 | 26 | with self.mock_role_methods('update_file', 'ensure_restart'): 27 | self.role.update_file.return_value = True 28 | 29 | self.role.ensure_vcl(template, varnish_vcl_path=varnish_vcl_path, options=options, owner=owner) 30 | 31 | self.role.update_file.assert_called_once_with(template, varnish_vcl_path, options=options, owner=owner, sudo=True) 32 | self.role.ensure_restart.assert_called_once_with() 33 | 34 | @istest 35 | def doesnt_restart_if_vcl_wasnt_updated(self): 36 | template = 'some-template' 37 | varnish_vcl_path = 'some-conf-path' 38 | options = {'foo': 'bar'} 39 | owner = 'some-owner' 40 | 41 | with self.mock_role_methods('update_file', 'ensure_restart'): 42 | self.role.update_file.return_value = False 43 | 44 | self.role.ensure_vcl(template, varnish_vcl_path=varnish_vcl_path, options=options, owner=owner) 45 | 46 | self.assertFalse(self.role.ensure_restart.called) 47 | 48 | @istest 49 | def updates_configuration_and_restarts(self): 50 | template = 'some-template' 51 | varnish_conf_path = 'some-conf-path' 52 | options = {'foo': 'bar'} 53 | owner = 'some-owner' 54 | 55 | with self.mock_role_methods('update_file', 'ensure_restart'): 56 | self.role.update_file.return_value = True 57 | 58 | self.role.ensure_conf(template, varnish_conf_path=varnish_conf_path, options=options, owner=owner) 59 | 60 | self.role.update_file.assert_called_once_with(template, varnish_conf_path, options=options, owner=owner, sudo=True) 61 | self.role.ensure_restart.assert_called_once_with() 62 | 63 | @istest 64 | def doesnt_restart_if_configuration_wasnt_updated(self): 65 | template = 'some-template' 66 | varnish_conf_path = 'some-conf-path' 67 | options = {'foo': 'bar'} 68 | owner = 'some-owner' 69 | 70 | with self.mock_role_methods('update_file', 'ensure_restart'): 71 | self.role.update_file.return_value = False 72 | 73 | self.role.ensure_conf(template, varnish_conf_path=varnish_conf_path, options=options, owner=owner) 74 | 75 | self.assertFalse(self.role.ensure_restart.called) 76 | 77 | @istest 78 | def doesnt_restart_if_not_necessary_upon_cleanup(self): 79 | with self.mock_role_method('restart'): 80 | self.role.cleanup() 81 | 82 | self.assertFalse(self.role.restart.called) 83 | 84 | @istest 85 | def restart_if_necessary_upon_cleanup(self): 86 | self.role.context['must-restart-varnish'] = True 87 | 88 | with self.mock_role_method('restart'): 89 | self.role.cleanup() 90 | 91 | self.assertTrue(self.role.restart.called) 92 | 93 | @istest 94 | def ensures_varnish_is_restarted(self): 95 | self.role.context['must-restart-varnish'] = False 96 | 97 | self.role.ensure_restart() 98 | 99 | self.assertTrue(self.role.context['must-restart-varnish']) 100 | 101 | @istest 102 | def restarts_varnish(self): 103 | with self.execute_mock(): 104 | self.role.restart() 105 | 106 | self.role.execute.assert_called_once_with('START=yes /etc/init.d/varnish restart', sudo=True) 107 | -------------------------------------------------------------------------------- /provy/more/centos/vcs/git.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `Git `_ repository creation operations within CentOS distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.centos.package.yum import YumRole 10 | 11 | 12 | class GitRole(Role): 13 | ''' 14 | This role provides utility methods for `Git `_ repositories management within CentOS distributions. 15 | 16 | Example: 17 | :: 18 | 19 | from provy.core import Role 20 | from provy.more.centos import GitRole 21 | 22 | class MySampleRole(Role): 23 | def provision(self): 24 | with self.using(GitRole) as role: 25 | role.ensure_repository('git://github.com/python-provy/provy.git', '/home/user/provy', 26 | owner='user', branch='some-branch') 27 | ''' 28 | 29 | def provision(self): 30 | ''' 31 | Installs `Git `_ dependencies. 32 | This method should be called upon if overriden in base classes, or `Git `_ won't work properly in the remote server. 33 | 34 | Example: 35 | :: 36 | 37 | class MySampleRole(Role): 38 | def provision(self): 39 | self.provision_role(GitRole) # does not need to be called if using with block. 40 | ''' 41 | with self.using(YumRole) as role: 42 | role.ensure_up_to_date() 43 | role.ensure_package_installed('git-core') 44 | 45 | def ensure_repository(self, repo, path, owner=None, branch=None, sudo=True): 46 | ''' 47 | Makes sure the repository is create in the remote server. 48 | This method does not update the repository or perform any operations in it. It is merely used to ensure that the repository exists in the specified path. 49 | 50 | :param repo: Git repository url. 51 | :type repo: :class:`str` 52 | :param path: Path to create the local repository. 53 | :type path: :class:`str` 54 | :param owner: User that owns the repository directory. Defaults to :data:`None`, using the current one in the remote server. 55 | :type owner: :class:`str` 56 | :param branch: If specified, the given branch will be checked-out, otherwise it stays in the master branch. 57 | :type branch: :class:`str` 58 | :param sudo: If :data:`False`, won't sudo when creating the repository. Defaults to :data:`True`. 59 | :type sudo: :class:`bool` 60 | 61 | Example: 62 | :: 63 | 64 | from provy.core import Role 65 | from provy.more.centos import GitRole 66 | 67 | class MySampleRole(Role): 68 | def provision(self): 69 | with self.using(GitRole) as role: 70 | role.ensure_repository('git://github.com/python-provy/provy.git', '/home/user/provy', 71 | owner='user', branch='some-branch') 72 | ''' 73 | self.__clone_repository(path, repo, sudo, owner) 74 | self.__checkout_branch(branch, path, repo, sudo, owner) 75 | self.__normalize_ownership(owner, path) 76 | 77 | def __normalize_ownership(self, owner, path): 78 | if owner: 79 | self.change_path_owner(path, owner) 80 | 81 | def __checkout_branch(self, branch, path, repo, sudo, owner): 82 | branch_name = "# On branch %s" % branch 83 | if branch and not branch_name in self.execute("git --git-dir=\"%s/.git\" --work-tree=\"%s\" status" % (path, path), 84 | sudo=True, stdout=False): 85 | self.log("Repository for %s is not in branch %s ! Switching..." % (repo, branch)) 86 | self.execute('git --git-dir="%s/.git" --work-tree="%s" checkout %s' % (path, path, branch), sudo=sudo, user=owner) 87 | self.log("Repository %s currently in branch %s!" % (repo, branch)) 88 | 89 | def __clone_repository(self, path, repo, sudo, owner): 90 | if not self.remote_exists_dir(path): 91 | self.log("Repository for %s does not exist! Cloning..." % repo) 92 | self.execute("git clone %s %s" % (repo, path), sudo=sudo, stdout=False, user=owner) 93 | self.log("Repository %s cloned!" % repo) 94 | -------------------------------------------------------------------------------- /provy/core/runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | This is the internal module responsible for running provy over the provyfile that was provided. 6 | 7 | It's recommended not to tinker with this module, as it might prevent your provyfile from working. 8 | ''' 9 | 10 | from os.path import abspath, dirname, join 11 | 12 | from fabric.context_managers import settings as _settings 13 | 14 | from provy.core.utils import import_module, AskFor, provyfile_module_from 15 | from provy.core.errors import ConfigurationError 16 | from jinja2 import FileSystemLoader, ChoiceLoader 17 | 18 | 19 | def run(provfile_path, server_name, password, extra_options): 20 | module_name = provyfile_module_from(provfile_path) 21 | prov = import_module(module_name) 22 | servers = get_servers_for(prov, server_name) 23 | 24 | build_prompt_options(servers, extra_options) 25 | 26 | for server in servers: 27 | provision_server(server, provfile_path, password, prov) 28 | 29 | 30 | def print_header(msg): 31 | print 32 | print "*" * len(msg) 33 | print msg 34 | print "*" * len(msg) 35 | 36 | 37 | def provision_server(server, provfile_path, password, prov): 38 | host_string = "%s@%s" % (server['user'], server['address'].strip()) 39 | 40 | context = { 41 | 'abspath': dirname(abspath(provfile_path)), 42 | 'path': dirname(provfile_path), 43 | 'owner': server['user'], 44 | 'cleanup': [], 45 | 'registered_loaders': [] 46 | } 47 | 48 | aggregate_node_options(server, context) 49 | 50 | loader = ChoiceLoader([ 51 | FileSystemLoader(join(context['abspath'], 'files')) 52 | ]) 53 | context['loader'] = loader 54 | 55 | print_header("Provisioning %s..." % host_string) 56 | 57 | settings_dict = dict(host_string=host_string, password=password) 58 | if 'ssh_key' in server and server['ssh_key']: 59 | settings_dict['key_filename'] = server['ssh_key'] 60 | 61 | with _settings(**settings_dict): 62 | context['host'] = server['address'] 63 | context['user'] = server['user'] 64 | role_instances = [] 65 | 66 | try: 67 | for role in server['roles']: 68 | context['role'] = role 69 | instance = role(prov, context) 70 | role_instances.append(instance) 71 | instance.provision() 72 | finally: 73 | for role in role_instances: 74 | role.cleanup() 75 | 76 | for role in context['cleanup']: 77 | role.cleanup() 78 | 79 | print_header("%s provisioned!" % host_string) 80 | 81 | 82 | def aggregate_node_options(server, context): 83 | for key, value in server.get('options', {}).iteritems(): 84 | context[key] = value 85 | 86 | 87 | def build_prompt_options(servers, extra_options): 88 | for server in servers: 89 | for option_name, option in server.get('options', {}).iteritems(): 90 | if isinstance(option, AskFor): 91 | if option.key in extra_options: 92 | value = extra_options[option.key] 93 | else: 94 | value = option.get_value(server) 95 | server['options'][option_name] = value 96 | 97 | 98 | def get_servers_for(prov, server_name): 99 | return get_items(prov, server_name, 'servers', lambda item: isinstance(item, dict) and 'address' in item) 100 | 101 | 102 | def get_items(prov, item_name, item_key, test_func): 103 | if not hasattr(prov, item_key): 104 | raise ConfigurationError('The %s collection was not found in the provyfile file.' % item_key) 105 | 106 | items = getattr(prov, item_key) 107 | 108 | for item_part in item_name.split('.'): 109 | items = items[item_part] 110 | 111 | found_items = [] 112 | recurse_items(items, test_func, found_items) 113 | return found_items 114 | 115 | 116 | def recurse_items(col, test_func, found_items): 117 | if not isinstance(col, dict): 118 | return 119 | 120 | if test_func(col): 121 | found_items.append(col) 122 | else: 123 | for key, val in col.iteritems(): 124 | if test_func(val): 125 | found_items.append(val) 126 | else: 127 | recurse_items(val, test_func, found_items) 128 | -------------------------------------------------------------------------------- /provy/more/debian/vcs/git.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provide `Git `_ repository creation operations within Debian distributions. 6 | ''' 7 | 8 | from provy.core import Role 9 | from provy.more.debian.package.aptitude import AptitudeRole 10 | 11 | 12 | class GitRole(Role): 13 | ''' 14 | This role provides utility methods for `Git `_ repositories management within Debian distributions. 15 | 16 | Example: 17 | :: 18 | 19 | from provy.core import Role 20 | from provy.more.debian import GitRole 21 | 22 | class MySampleRole(Role): 23 | def provision(self): 24 | with self.using(GitRole) as role: 25 | role.ensure_repository('git://github.com/python-provy/provy.git', '/home/user/provy', 26 | owner='user', branch='some-branch') 27 | ''' 28 | 29 | def provision(self): 30 | ''' 31 | Installs `Git `_ dependencies. 32 | This method should be called upon if overriden in base classes, or `Git `_ won't work properly in the remote server. 33 | 34 | Example: 35 | :: 36 | 37 | class MySampleRole(Role): 38 | def provision(self): 39 | self.provision_role(GitRole) # does not need to be called if using with block. 40 | ''' 41 | with self.using(AptitudeRole) as role: 42 | role.ensure_up_to_date() 43 | role.ensure_package_installed('git-core') 44 | 45 | def ensure_repository(self, repo, path, owner=None, branch=None, sudo=True): 46 | ''' 47 | Makes sure the repository is create in the remote server. 48 | This method does not update the repository or perform any operations in it. It is merely used to ensure that the repository exists in the specified path. 49 | 50 | :param repo: Git repository url. 51 | :type repo: :class:`str` 52 | :param path: Path to create the local repository. 53 | :type path: :class:`str` 54 | :param owner: User that owns the repository directory. Defaults to :data:`None`, using the current one in the remote server. 55 | :type owner: :class:`str` 56 | :param branch: If specified, the given branch will be checked-out, otherwise it stays in the master branch. 57 | :type branch: :class:`str` 58 | :param sudo: If :data:`False`, won't sudo when creating the repository. Defaults to :data:`True`. 59 | :type sudo: :class:`bool` 60 | 61 | Example: 62 | :: 63 | 64 | from provy.core import Role 65 | from provy.more.debian import GitRole 66 | 67 | class MySampleRole(Role): 68 | def provision(self): 69 | with self.using(GitRole) as role: 70 | role.ensure_repository('git://github.com/python-provy/provy.git', '/home/user/provy', 71 | owner='user', branch='some-branch') 72 | ''' 73 | self.__clone_repository(path, repo, sudo, owner) 74 | self.__checkout_branch(branch, path, repo, sudo, owner) 75 | self.__normalize_ownership(owner, path) 76 | 77 | def __normalize_ownership(self, owner, path): 78 | if owner: 79 | self.change_path_owner(path, owner) 80 | 81 | def __checkout_branch(self, branch, path, repo, sudo, owner): 82 | branch_name = "# On branch %s" % branch 83 | if branch and not branch_name in self.execute("git --git-dir=\"%s/.git\" --work-tree=\"%s\" status" % (path, path), 84 | sudo=True, stdout=False): 85 | self.log("Repository for %s is not in branch %s ! Switching..." % (repo, branch)) 86 | self.execute('git --git-dir="%s/.git" --work-tree="%s" checkout %s' % (path, path, branch), sudo=sudo, user=owner) 87 | self.log("Repository %s currently in branch %s!" % (repo, branch)) 88 | 89 | def __clone_repository(self, path, repo, sudo, owner): 90 | if not self.remote_exists_dir(path): 91 | self.log("Repository for %s does not exist! Cloning..." % repo) 92 | self.execute("git clone %s %s" % (repo, path), sudo=sudo, stdout=False, user=owner) 93 | self.log("Repository %s cloned!" % repo) 94 | -------------------------------------------------------------------------------- /tests/unit/more/debian/programming/test_nodejs.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | from mock import patch, call 4 | from nose.tools import istest 5 | 6 | from provy.more.debian import AptitudeRole, NodeJsRole 7 | from tests.unit.tools.helpers import ProvyTestCase 8 | 9 | 10 | class NodeJsRoleTest(ProvyTestCase): 11 | def setUp(self): 12 | super(NodeJsRoleTest, self).setUp() 13 | self.role = NodeJsRole(prov=None, context={}) 14 | 15 | @contextmanager 16 | def node_method(self, method_name): 17 | with patch('provy.more.debian.NodeJsRole.%s' % method_name) as mock: 18 | yield mock 19 | 20 | @istest 21 | def adds_repositories_and_installs_necessary_sources_to_provision_to_debian(self): 22 | with self.execute_mock() as execute, self.using_stub(AptitudeRole) as mock_aptitude, self.mock_role_method('ensure_dir') as ensure_dir: 23 | self.role.provision_to_debian() 24 | 25 | mock_aptitude.ensure_package_installed.assert_called_with('g++') 26 | ensure_dir.assert_called_with('/tmp/nodejs', sudo=True) 27 | 28 | execute.assert_has_calls([ 29 | call('wget -N http://nodejs.org/dist/node-latest.tar.gz', sudo=True), 30 | call('tar xzvf node-latest.tar.gz && cd `ls -rd node-v*` && ./configure && make install', sudo=True), 31 | ]) 32 | 33 | @istest 34 | def adds_repositories_and_installs_necessary_packages_to_provision_to_ubuntu(self): 35 | with self.execute_mock() as execute, self.using_stub(AptitudeRole) as mock_aptitude: 36 | self.role.provision_to_ubuntu() 37 | 38 | mock_aptitude.ensure_package_installed.assert_any_call('python-software-properties') 39 | execute.assert_called_with('add-apt-repository ppa:chris-lea/node.js', sudo=True) 40 | self.assertTrue(mock_aptitude.force_update.called) 41 | mock_aptitude.ensure_package_installed.assert_any_call('nodejs') 42 | mock_aptitude.ensure_package_installed.assert_any_call('npm') 43 | mock_aptitude.ensure_package_installed.assert_any_call('nodejs-dev') 44 | 45 | @istest 46 | def checks_that_node_is_already_installed(self): 47 | with self.execute_mock() as execute, self.warn_only(): 48 | execute.return_value = 'v0.8.10' 49 | self.assertTrue(self.role.is_already_installed()) 50 | 51 | @istest 52 | def checks_that_node_is_not_installed_yet_by_output_string(self): 53 | with self.execute_mock() as execute, self.warn_only(): 54 | execute.return_value = 'command not found' 55 | self.assertFalse(self.role.is_already_installed()) 56 | 57 | @istest 58 | def checks_that_node_is_not_installed_yet_by_stranger_output_string(self): 59 | with self.execute_mock() as execute, self.warn_only(): 60 | execute.return_value = 'verbose error: command not found' 61 | self.assertFalse(self.role.is_already_installed()) 62 | 63 | @istest 64 | def checks_that_node_is_not_installed_yet_by_output_as_none(self): 65 | with self.execute_mock() as execute, self.warn_only(): 66 | execute.return_value = None 67 | self.assertFalse(self.role.is_already_installed()) 68 | 69 | @istest 70 | def provisions_to_debian_if_is_debian(self): 71 | with self.provisioning_to('debian'), self.node_method('provision_to_debian') as provision_to_debian, self.node_method('is_already_installed') as is_already_installed: 72 | is_already_installed.return_value = False 73 | self.role.provision() 74 | provision_to_debian.assert_called_with() 75 | 76 | @istest 77 | def provisions_to_ubuntu_if_is_ubuntu(self): 78 | with self.provisioning_to('ubuntu'), self.node_method('provision_to_ubuntu') as provision_to_ubuntu, self.node_method('is_already_installed') as is_already_installed: 79 | is_already_installed.return_value = False 80 | self.role.provision() 81 | provision_to_ubuntu.assert_called_with() 82 | 83 | @istest 84 | def doesnt_provision_if_already_installed(self): 85 | with self.provisioning_to('ubuntu'), self.node_method('provision_to_ubuntu') as provision_to_ubuntu, self.node_method('is_already_installed') as is_already_installed: 86 | is_already_installed.return_value = True 87 | self.role.provision() 88 | self.assertFalse(provision_to_ubuntu.called) 89 | -------------------------------------------------------------------------------- /tests/unit/more/debian/package/test_npm.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import NodeJsRole, NPMRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class NPMRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(NPMRoleTest, self).setUp() 11 | self.role = NPMRole(prov=None, context={}) 12 | 13 | @istest 14 | def provisions_node_js_as_dependency(self): 15 | with self.mock_role_method('provision_role') as provision_role: 16 | self.role.provision() 17 | 18 | provision_role.assert_called_with(NodeJsRole) 19 | 20 | @istest 21 | def checks_that_a_package_is_installed_by_name(self): 22 | with self.execute_mock() as execute: 23 | execute.return_value = 'socket.io' 24 | 25 | self.assertTrue(self.role.is_package_installed('socket.io')) 26 | 27 | execute.assert_called_with("npm --global list | egrep 'socket.io'", stdout=False, sudo=True) 28 | 29 | @istest 30 | def checks_that_a_package_is_not_installed_by_name(self): 31 | with self.execute_mock() as execute: 32 | execute.return_value = '' 33 | 34 | self.assertFalse(self.role.is_package_installed('socket.io')) 35 | 36 | execute.assert_called_with("npm --global list | egrep 'socket.io'", stdout=False, sudo=True) 37 | 38 | @istest 39 | def checks_that_a_package_is_installed_by_name_and_version(self): 40 | with self.execute_mock() as execute: 41 | execute.return_value = 'socket.io@0.6.17' 42 | 43 | self.assertTrue(self.role.is_package_installed('socket.io', '0.6.17')) 44 | 45 | execute.assert_called_with("npm --global list | egrep 'socket.io@0.6.17'", stdout=False, sudo=True) 46 | 47 | @istest 48 | def checks_that_a_package_is_not_installed_by_name_and_version(self): 49 | with self.execute_mock() as execute: 50 | execute.return_value = '' 51 | 52 | self.assertFalse(self.role.is_package_installed('socket.io', '0.6.17')) 53 | 54 | execute.assert_called_with("npm --global list | egrep 'socket.io@0.6.17'", stdout=False, sudo=True) 55 | 56 | @istest 57 | def installs_a_package_if_its_not_installed_yet_by_name(self): 58 | with self.execute_mock() as execute, patch('provy.more.debian.NPMRole.is_package_installed') as is_package_installed: 59 | is_package_installed.return_value = False 60 | self.role.ensure_package_installed('socket.io') 61 | 62 | execute.assert_called_with('npm install --global socket.io', stdout=False, sudo=True) 63 | 64 | @istest 65 | def doesnt_install_a_package_if_its_already_installed_yet_by_name(self): 66 | with self.execute_mock() as execute, patch('provy.more.debian.NPMRole.is_package_installed') as is_package_installed: 67 | is_package_installed.return_value = True 68 | self.role.ensure_package_installed('socket.io') 69 | 70 | self.assertFalse(execute.called) 71 | 72 | @istest 73 | def installs_a_package_if_its_not_installed_yet_by_name_and_version(self): 74 | with self.execute_mock() as execute, patch('provy.more.debian.NPMRole.is_package_installed') as is_package_installed: 75 | is_package_installed.return_value = False 76 | self.role.ensure_package_installed('socket.io', '0.6.17') 77 | 78 | execute.assert_called_with('npm install --global socket.io@0.6.17', stdout=False, sudo=True) 79 | 80 | @istest 81 | def installs_a_package_if_its_not_installed_yet_by_name_and_version_with_stdout(self): 82 | with self.execute_mock() as execute, patch('provy.more.debian.NPMRole.is_package_installed') as is_package_installed: 83 | is_package_installed.return_value = False 84 | self.role.ensure_package_installed('socket.io', '0.6.17', stdout=True) 85 | 86 | execute.assert_called_with('npm install --global socket.io@0.6.17', stdout=True, sudo=True) 87 | 88 | @istest 89 | def installs_a_package_if_its_not_installed_yet_by_name_and_version_without_sudo(self): 90 | with self.execute_mock() as execute, patch('provy.more.debian.NPMRole.is_package_installed') as is_package_installed: 91 | is_package_installed.return_value = False 92 | self.role.ensure_package_installed('socket.io', '0.6.17', sudo=False) 93 | 94 | execute.assert_called_with('npm install --global socket.io@0.6.17', stdout=False, sudo=False) 95 | -------------------------------------------------------------------------------- /tests/unit/more/debian/security/test_selinux.py: -------------------------------------------------------------------------------- 1 | from mock import call, patch 2 | from nose.tools import istest 3 | 4 | from provy.more.debian import AptitudeRole, SELinuxRole 5 | from tests.unit.tools.helpers import ProvyTestCase 6 | 7 | 8 | class SELinuxRoleTest(ProvyTestCase): 9 | def setUp(self): 10 | super(SELinuxRoleTest, self).setUp() 11 | self.role = SELinuxRole(prov=None, context={'cleanup': []}) 12 | 13 | @istest 14 | def provisions_correctly(self): 15 | with self.mock_role_methods('install_packages', 'activate'): 16 | self.role.provision() 17 | 18 | self.role.install_packages.assert_called_with() 19 | self.role.activate.assert_called_with() 20 | 21 | @istest 22 | def installs_packages_in_debian(self): 23 | with self.using_stub(AptitudeRole) as aptitude, self.provisioning_to('debian'): 24 | self.role.install_packages() 25 | 26 | expected_packages = [ 27 | call('selinux-basics'), 28 | call('selinux-policy-default'), 29 | call('selinux-utils'), 30 | call('auditd'), 31 | call('audispd-plugins'), 32 | ] 33 | self.assertEqual(aptitude.ensure_package_installed.mock_calls, expected_packages) 34 | 35 | @istest 36 | def installs_packages_in_ubuntu(self): 37 | with self.using_stub(AptitudeRole) as aptitude, self.provisioning_to('ubuntu'): 38 | self.role.install_packages() 39 | 40 | expected_packages = [ 41 | call('selinux'), 42 | call('selinux-utils'), 43 | call('auditd'), 44 | call('audispd-plugins'), 45 | ] 46 | self.assertEqual(aptitude.ensure_package_installed.mock_calls, expected_packages) 47 | 48 | @istest 49 | def activates_on_debian(self): 50 | with self.execute_mock() as execute, self.provisioning_to('debian'), patch.object(self.role, 'enforce'): 51 | self.role.activate() 52 | 53 | expected_calls = [ 54 | call('selinux-activate', stdout=False, sudo=True), 55 | call("semanage login -m -s 'user_u' -r s0 __default__", stdout=False, sudo=True), 56 | ] 57 | self.assertEqual(execute.mock_calls, expected_calls) 58 | self.role.enforce.assert_called_with() 59 | 60 | @istest 61 | def activates_on_ubuntu(self): 62 | with self.execute_mock() as execute, self.provisioning_to('ubuntu'), patch.object(self.role, 'enforce'): 63 | self.role.activate() 64 | 65 | expected_calls = [ 66 | call("semanage login -m -s 'user_u' -r s0 __default__", stdout=False, sudo=True), 67 | ] 68 | self.assertEqual(execute.mock_calls, expected_calls) 69 | self.role.enforce.assert_called_with() 70 | 71 | @istest 72 | def puts_environment_in_enforce_mode(self): 73 | with self.execute_mock(), self.mock_role_method('ensure_line'), self.warn_only(): 74 | self.role.enforce() 75 | 76 | self.role.execute.assert_called_with('setenforce 1', stdout=False, sudo=True) 77 | self.role.ensure_line.assert_called_with('SELINUX=enforcing', '/etc/selinux/config', sudo=True) 78 | 79 | @istest 80 | def ensures_that_a_login_mapping_exists(self): 81 | with self.execute_mock() as execute, self.warn_only(): 82 | self.role.ensure_login_mapping('foo') 83 | 84 | execute.assert_called_with('semanage login -a foo', stdout=False, sudo=True) 85 | 86 | @istest 87 | def maps_a_login_user_to_an_selinux_user(self): 88 | with self.execute_mock() as execute, patch.object(self.role, 'ensure_login_mapping'): 89 | self.role.map_login('foo', 'staff_u') 90 | 91 | self.role.ensure_login_mapping.assert_called_with('foo') 92 | execute.assert_called_with('semanage login -m -s staff_u foo', stdout=False, sudo=True) 93 | 94 | @istest 95 | def maps_a_login_user_to_selinux_roles(self): 96 | with self.execute_mock() as execute, patch.object(self.role, 'ensure_login_mapping'): 97 | self.role.map_role('foo', ['staff_r', 'sysadm_r']) 98 | 99 | self.role.ensure_login_mapping.assert_called_with('foo') 100 | execute.assert_called_with("semanage user -m -R 'staff_r sysadm_r' foo", stdout=False, sudo=True) 101 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions are very welcome. Specially roles. If you implement a role that you think others might be using, please contribute. 5 | 6 | To contribute head to `provy's github page `_, fork it and create a pull request. 7 | 8 | Developing 9 | ---------- 10 | 11 | Make it great :-) 12 | ***************** 13 | 14 | We strive to keep the internal quality of provy to the best that we can; 15 | Therefore, it's very important to keep some things in mind when contributing with code for provy: 16 | 17 | * Test everything you can, with automated tests. If possible, please develop code with `TDD `_. 18 | If you're having a hard time building tests, don't hesitate to ask for help in the `provy mailing list `_. 19 | We are happy to help you keep your code well-covered by tests; 20 | 21 | * When writing actual code, follow the conventions in `PEP 8 `_ 22 | (except for `maximum line length `_, 23 | which we don't follow because there are too many parts of the project that require large strings to be used); 24 | 25 | * When writing docstrings, follow the conventions in `PEP 257 `_ 26 | (take a look at other docstrings in the project to get a feel of how we organize them); 27 | 28 | - Also, when writing docstrings for the API, provide examples of how that method or class works. 29 | Having a code example of a part of the API is really helpful for the user. 30 | 31 | Setting up your environment 32 | *************************** 33 | 34 | 1. Make sure `pip `_, `virtualenv `_ and `virtualenvwrapper `_ are installed; 35 | 2. Create a virtual environment for provy: 36 | 37 | .. code-block:: sh 38 | 39 | $ mkvirtualenv provy 40 | 41 | 3. Install the requirements: 42 | 43 | .. code-block:: sh 44 | 45 | $ pip install -r REQUIREMENTS 46 | 47 | 4. Run your first provy build, to make sure everything's ready for you to start developing: 48 | 49 | .. code-block:: sh 50 | 51 | $ make build 52 | 53 | The command should run without accusing any error. 54 | 55 | How to develop 56 | ************** 57 | 58 | There are basically two commands we run, when developing. 59 | 60 | When building code, you need to test it and check if the code format is OK with the conventions we use: 61 | 62 | .. code-block:: sh 63 | 64 | $ make build 65 | 66 | This Makefile target essentially does these steps: 67 | 68 | 1. It runs the tests over the project; 69 | 2. It builds a code coverage report (you should take a look if the total code coverage is not decreasing, when you build your code); 70 | 3. It runs `flake8 `_ over the entire codebase, making sure the code style is following the conventions mentioned above. 71 | 72 | It's also important to keep the codebase well documented. We use Sphinx to generate the documentation, 73 | which is also used when our docs go to `Read The Docs `_. 74 | 75 | To build the docs in your environment, in order to test it locally (this is very useful to see how your docs will look like when they are rolled out), 76 | first go to the `provy/docs` directory, then run: 77 | 78 | .. code-block:: sh 79 | 80 | $ make html 81 | 82 | Some warnings may show up in the command output - you should listen to them, in order to spot possible documentation problems -. 83 | 84 | The team 85 | -------- 86 | 87 | The core team 88 | ************* 89 | 90 | The core team behind provy (in order of joining the project): 91 | 92 | * `Bernardo Heynemann `_ (technical leader of this project) 93 | * `Rafael Carício `_ 94 | * `Douglas Andrade `_ 95 | * `Thiago Avelino `_ 96 | * `Diogo Baeder `_ 97 | 98 | Other contributors 99 | ****************** 100 | 101 | Other non-core members, but equally important, equally rocking, equally ass-kicking contributors can be seen in this list: 102 | https://github.com/python-provy/provy/network/members 103 | 104 | There are also some more contributors that haven't send code to the project, but who help in other ways, when and how they can. 105 | We're very happy to have you, guys! :-) -------------------------------------------------------------------------------- /provy/more/debian/package/npm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Roles in this namespace are meant to provision packages installed via the `NPM `_ package manager for Debian distributions. 6 | ''' 7 | 8 | from fabric.api import settings 9 | from provy.core import Role 10 | 11 | from provy.more.debian.programming.nodejs import NodeJsRole 12 | 13 | 14 | class NPMRole(Role): 15 | ''' 16 | This role provides package management operations with `NPM `_ within Debian distributions. 17 | 18 | Example: 19 | :: 20 | 21 | from provy.core import Role 22 | from provy.more.debian import NPMRole 23 | 24 | class MySampleRole(Role): 25 | def provision(self): 26 | with self.using(NPMRole) as role: 27 | role.ensure_package_installed('socket.io', '0.6.17') 28 | ''' 29 | 30 | time_format = "%d-%m-%y %H:%M:%S" 31 | key = 'npm-up-to-date' 32 | 33 | def provision(self): 34 | ''' 35 | Installs NPM. This method should be called upon if overriden in base classes, or NPM won't work properly in the remote server. 36 | 37 | Example: 38 | :: 39 | 40 | from provy.core import Role 41 | from provy.more.debian import NPMRole 42 | 43 | class MySampleRole(Role): 44 | def provision(self): 45 | self.provision_role(NPMRole) # no need to call this if using with block. 46 | ''' 47 | 48 | self.provision_role(NodeJsRole) 49 | 50 | def is_package_installed(self, package_name, version=None): 51 | ''' 52 | Returns :data:`True` if the given package is installed via NPM, :data:`False` otherwise. 53 | 54 | :param package_name: Name of the package to verify 55 | :type package_name: :class:`str` 56 | :param version: Version to check for. Defaults to :data:`None`, which makes it check for any version. 57 | :type version: :class:`str` 58 | :return: Whether the package is installed or not. 59 | :rtype: :class:`bool` 60 | 61 | Example: 62 | :: 63 | 64 | from provy.core import Role 65 | from provy.more.debian import NPMRole 66 | 67 | class MySampleRole(Role): 68 | def provision(self): 69 | with self.using(NPMRole) as role: 70 | if role.is_package_installed('socket.io', '0.6.17'): 71 | pass 72 | ''' 73 | 74 | with settings(warn_only=True): 75 | if version: 76 | package_name = "%s@%s" % (package_name, version) 77 | return package_name in self.execute("npm --global list | egrep '%s'" % package_name, stdout=False, sudo=True) 78 | 79 | def ensure_package_installed(self, package_name, version=None, stdout=False, sudo=True): 80 | ''' 81 | Ensures that the given package in the given version is installed via NPM. 82 | 83 | :param package_name: Name of the package to install. 84 | :type package_name: :class:`str` 85 | :param version: If specified, installs this version of the package. Installs latest version otherwise. 86 | :type version: :class:`str` 87 | :param stdout: Indicates whether install progress should be shown to stdout. Defaults to :data:`False`. 88 | :type stdout: :class:`bool` 89 | :param sudo: Indicates whether the package should be installed with the super user. Defaults to :data:`True`. 90 | :type sudo: :class:`bool` 91 | :return: Whether the package had to be installed or not. 92 | :rtype: :class:`bool` 93 | 94 | Example: 95 | :: 96 | 97 | from provy.core import Role 98 | from provy.more.debian import NPMRole 99 | 100 | class MySampleRole(Role): 101 | def provision(self): 102 | with self.using(NPMRole) as role: 103 | role.ensure_package_installed('socket.io', '0.6.17') 104 | ''' 105 | 106 | if not self.is_package_installed(package_name, version): 107 | if version: 108 | package_name = "%s@%s" % (package_name, version) 109 | 110 | self.log('%s is not installed (via NPM)! Installing...' % package_name) 111 | self.execute('npm install --global %s' % package_name, stdout=stdout, sudo=sudo) 112 | self.log('%s is installed (via NPM).' % package_name) 113 | return True 114 | return False 115 | -------------------------------------------------------------------------------- /docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from os.path import exists, join, abspath, sep, splitext, dirname 5 | from json import dumps 6 | 7 | from provy.core import Role 8 | 9 | import os 10 | import fnmatch 11 | import inspect 12 | 13 | 14 | class RoleDoc(object): 15 | def __init__(self, role, name, module, docs): 16 | self.role = role 17 | self.name = name 18 | self.module = module 19 | self.fullname = "%s.%s" % (module, name) 20 | self.docs = docs 21 | self.methods = [] 22 | self.parse_methods(role) 23 | 24 | def parse_methods(self, role): 25 | for name, member in inspect.getmembers(role): 26 | if not inspect.ismethod(member) or name.startswith('_'): 27 | continue 28 | if not member.__module__ == role.__module__: 29 | continue 30 | if not member.__doc__: 31 | print "Warning: Method %s of role %s does not have docstring." % (name, role.__name__) 32 | self.add_method(NameDoc(name, member.__doc__)) 33 | 34 | def add_method(self, method_doc): 35 | self.methods.append(method_doc) 36 | 37 | def to_dict(self): 38 | obj = { 39 | '__name__': self.name, 40 | '__fullName__': self.fullname, 41 | '__module__': self.module, 42 | '__doc__': self.docs and self.docs.strip() or None, 43 | '__methods__': [] 44 | } 45 | for method in self.methods: 46 | obj['__methods__'].append(method.to_dict()) 47 | 48 | return obj 49 | 50 | 51 | class NameDoc(object): 52 | def __init__(self, name, doc): 53 | self.name = name 54 | self.doc = doc 55 | 56 | def to_dict(self): 57 | return { 58 | '__name__': self.name, 59 | '__doc__': self.doc and self.doc.strip() or None 60 | } 61 | 62 | 63 | def main(): 64 | path = "/tmp/docs.json" 65 | source_path = join(os.curdir, 'provy', 'more') 66 | 67 | if not exists(dirname(path)): 68 | os.makedirs(dirname(path)) 69 | 70 | root_namespace = 'provy.more' 71 | 72 | roles_to_document = { 73 | 'Role': RoleDoc(Role, 'Role', 'provy.core.roles', Role.__doc__) 74 | } 75 | 76 | for root, dirs, files in os.walk(source_path): 77 | for file_name in files: 78 | if file_name == "__init__.py": 79 | continue 80 | if not fnmatch.fnmatch(file_name, '*.py'): 81 | continue 82 | 83 | module_path = '%s.%s.%s' % (root_namespace, 84 | get_namespace_for(root), 85 | splitext(file_name)[0]) 86 | 87 | module = import_module(module_path) 88 | 89 | for name, member in inspect.getmembers(module): 90 | if not inspect.isclass(member) or not issubclass(member, Role): 91 | continue 92 | if member.__module__ != module_path: 93 | continue 94 | 95 | if not member.__doc__: 96 | print "Warning: Role %s.%s does not have docstring." % (member.__module__, name) 97 | 98 | roles_to_document[module_path] = RoleDoc(member, 99 | name, 100 | member.__module__, 101 | member.__doc__) 102 | 103 | tree = {} 104 | 105 | for full_name, role_doc in roles_to_document.iteritems(): 106 | role = role_doc.role 107 | name = role_doc.name 108 | current = tree 109 | module = __import__(role.__module__) 110 | for part in role.__module__.split('.'): 111 | if hasattr(module, part): 112 | module = getattr(module, part) 113 | if not part in current: 114 | if not module.__doc__: 115 | print "Warning: Module %s does not have docstring." % module.__name__ 116 | 117 | current[part] = { 118 | '__name__': module.__name__, 119 | '__doc__': module.__doc__ and module.__doc__.strip() or None 120 | } 121 | if part == role.__module__.split('.')[-1]: 122 | current[part][role_doc.name] = role_doc.to_dict() 123 | current = current[part] 124 | 125 | contents = dumps(tree, sort_keys=True, separators=(',', ':')) 126 | with open(path, 'w') as f: 127 | f.write(contents) 128 | 129 | 130 | def import_module(module_path): 131 | module = __import__(module_path) 132 | return reduce(getattr, module_path.split('.')[1:], module) 133 | 134 | 135 | def get_namespace_for(directory): 136 | source_path = abspath(join(os.curdir, 'provy', 'more')) 137 | diff = abspath(directory).replace(source_path, '') 138 | namespace = '.'.join([module for module in diff.split(sep) if module]) 139 | return namespace 140 | 141 | if __name__ == '__main__': 142 | main() 143 | --------------------------------------------------------------------------------