├── 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 | [](http://travis-ci.org/python-provy/provy)
11 | [](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 |
--------------------------------------------------------------------------------