diff options
-rw-r--r-- | LICENSE.md | 13 | ||||
-rw-r--r-- | Modulefile | 9 | ||||
-rw-r--r-- | README.md | 101 | ||||
-rw-r--r-- | Rakefile | 8 | ||||
-rw-r--r-- | manifests/config.pp | 24 | ||||
-rw-r--r-- | manifests/gunicorn.pp | 65 | ||||
-rw-r--r-- | manifests/init.pp | 50 | ||||
-rw-r--r-- | manifests/install.pp | 31 | ||||
-rw-r--r-- | manifests/pip.pp | 64 | ||||
-rw-r--r-- | manifests/requirements.pp | 68 | ||||
-rw-r--r-- | manifests/virtualenv.pp | 82 | ||||
-rw-r--r-- | templates/gunicorn.erb | 35 | ||||
-rw-r--r-- | tests/gunicorn.pp | 14 | ||||
-rw-r--r-- | tests/init.pp | 5 | ||||
-rw-r--r-- | tests/pip.pp | 10 | ||||
-rw-r--r-- | tests/requirements.pp | 10 | ||||
-rw-r--r-- | tests/virtualenv.pp | 12 |
17 files changed, 601 insertions, 0 deletions
diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..048a291 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,13 @@ +Copyright © 2012 Sergey Stankevich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Modulefile b/Modulefile new file mode 100644 index 0000000..c7f2f97 --- /dev/null +++ b/Modulefile @@ -0,0 +1,9 @@ +name 'puppet-python' +version '1.0.0' + +author 'Sergey Stankevich' +license 'Apache License, Version 2.0' +project_page 'https://github.com/stankevich/puppet-python' +source 'git://github.com/stankevich/puppet-python' +summary 'Puppet module for Python' +description 'Installs and manages Python, pip, virtualenv, Gunicorn virtual hosts.' diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7b00d5 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +[puppet-python](https://github.com/stankevich/puppet-python) +====== + +Puppet module for installing and managing python, pip, virtualenv, Gunicorn virtual hosts + +## Usage + +### python + +Installs and manages python, python-dev, python-virtualenv and Gunicorn. + +**version** — Python version to install. Default: system default + +**dev** — Install python-dev. Default: false + +**virtualenv** — Install python-virtualenv. Default: false + +**gunicorn** — Install Gunicorn. Default: false + + class { 'python': + version => 'system', + dev => true, + virtualenv => true, + gunicorn => true, + } + +### python::pip + +Installs and manages packages from pip. + +**ensure** — present/absent. Default: present + +**virtualenv** — virtualenv to run pip in. + +**proxy** — Proxy server to use for outbound connections. Default: none + + python::pip { 'flask': + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', + } + +### python::requirements + +Installs and manages Python packages from requirements file. + +**virtualenv** — virtualenv to run pip in. Default: system-wide + +**proxy** — Proxy server to use for outbound connections. Default: none + + python::requirements { '/var/www/project1/requirements.txt': + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', + } + +### python::virtualenv + +Creates Python virtualenv. + +**ensure** — present/absent. Default: present + +**version** — Python version to use. Default: system default + +**requirements** — Path to pip requirements.txt file. Default: none + +**proxy** — Proxy server to use for outbound connections. Default: none + + python::virtualenv { '/var/www/project1': + ensure => present, + version => 'system', + requirements => '/var/www/project1/requirements.txt', + proxy => 'http://proxy.domain.com:3128', + } + +### python::gunicorn + +Manages Gunicorn virtual hosts. + +**ensure** — present/absent. Default: present + +**virtualenv** — Run in virtualenv, specify directory. Default: disabled + +**mode** — Gunicorn mode. wsgi/django. Default: wsgi + +**dir** — Application directory. + +**bind** — Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. Default: unix:/tmp/gunicorn-$name.socket or unix:${virtualenv}/${name}.socket + +**environment** — Set ENVIRONMENT variable. Default: none + + python::gunicorn { 'vhost': + ensure => present, + virtualenv => '/var/www/project1', + mode => 'wsgi', + dir => '/var/www/project1/current', + bind => 'unix:/tmp/gunicorn.socket', + environment => 'prod', + } + +## Authors + +[Sergey Stankevich](https://github.com/stankevich) diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..58df3ec --- /dev/null +++ b/Rakefile @@ -0,0 +1,8 @@ +# Rakefile for puppet-lint (https://github.com/rodjek/puppet-lint) +# Run: rake lint + +require 'puppet-lint/tasks/puppet-lint' +PuppetLint.configuration.with_filename = true +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_class_parameter_defaults') +PuppetLint.configuration.send('disable_80chars') diff --git a/manifests/config.pp b/manifests/config.pp new file mode 100644 index 0000000..43da22e --- /dev/null +++ b/manifests/config.pp @@ -0,0 +1,24 @@ +class python::config { + + Class['python::install'] -> Python::Pip <| |> + Class['python::install'] -> Python::Requirements <| |> + Class['python::install'] -> Python::Virtualenv <| |> + + Python::Virtualenv <| |> -> Python::Pip <| |> + Python::Virtualenv <| |> -> Python::Requirements <| |> + + if $python::gunicorn { + Class['python::install'] -> Python::Gunicorn <| |> + + Python::Gunicorn <| |> ~> Service['gunicorn'] + + service { 'gunicorn': + ensure => running, + enable => true, + hasrestart => true, + hasstatus => false, + pattern => '/usr/bin/gunicorn', + } + } + +} diff --git a/manifests/gunicorn.pp b/manifests/gunicorn.pp new file mode 100644 index 0000000..d328d75 --- /dev/null +++ b/manifests/gunicorn.pp @@ -0,0 +1,65 @@ +# == Define: python::gunicorn +# +# Manages Gunicorn virtual hosts. +# +# === Parameters +# +# [*ensure*] +# present|absent. Default: present +# +# [*virtualenv*] +# Run in virtualenv, specify directory. Default: disabled +# +# [*mode*] +# Gunicorn mode. +# wsgi|django. Default: wsgi +# +# [*dir*] +# Application directory. +# +# [*bind*] +# Bind on: 'HOST', 'HOST:PORT', 'unix:PATH'. +# Default: system-wide: unix:/tmp/gunicorn-$name.socket +# virtualenv: unix:${virtualenv}/${name}.socket +# +# [*environment*] +# Set ENVIRONMENT variable. Default: none +# +# === Examples +# +# python::gunicorn { 'vhost': +# ensure => present, +# virtualenv => '/var/www/project1', +# mode => 'wsgi', +# dir => '/var/www/project1/current', +# bind => 'unix:/tmp/gunicorn.socket', +# environment => 'prod', +# } +# +# === Authors +# +# Sergey Stankevich +# +define python::gunicorn ( + $ensure = present, + $virtualenv = false, + $mode = 'wsgi', + $dir = false, + $bind = false, + $environment = false +) { + + # Parameter validation + if ! $dir { + fail('python::gunicorn: dir parameter must not be empty') + } + + file { "/etc/gunicorn.d/${name}": + ensure => $ensure, + mode => '0644', + owner => 'root', + group => 'root', + content => template('python/gunicorn.erb'), + } + +} diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..c7ab10f --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,50 @@ +# == Class: python +# +# Installs and manages python, python-dev, python-virtualenv and Gunicorn. +# +# === Parameters +# +# [*version*] +# Python version to install. Default: system default +# +# [*dev*] +# Install python-dev. Default: false +# +# [*virtualenv*] +# Install python-virtualenv. Default: false +# +# [*gunicorn*] +# Install Gunicorn. Default: false +# +# === Examples +# +# class { 'python': +# version => 'system', +# dev => true, +# virtualenv => true, +# gunicorn => true, +# } +# +# === Authors +# +# Sergey Stankevich +# +class python ( + $version = 'system', + $dev = false, + $virtualenv = false, + $gunicorn = false +) { + + # Module compatibility check + $compatible = [ 'Debian', 'Ubuntu' ] + if ! ($::operatingsystem in $compatible) { + fail("Module is not compatible with ${::operatingsystem}") + } + + Class['python::install'] -> Class['python::config'] + + include python::install + include python::config + +} diff --git a/manifests/install.pp b/manifests/install.pp new file mode 100644 index 0000000..dec99b8 --- /dev/null +++ b/manifests/install.pp @@ -0,0 +1,31 @@ +class python::install { + + $python = $python::version ? { + 'system' => 'python', + default => "python${python::version}", + } + + package { $python: ensure => present } + + $dev_ensure = $python::dev ? { + true => present, + default => absent, + } + + package { "${python}-dev": ensure => $dev_ensure } + + $venv_ensure = $python::virtualenv ? { + true => present, + default => absent, + } + + package { 'python-virtualenv': ensure => $venv_ensure } + + $gunicorn_ensure = $python::gunicorn ? { + true => present, + default => absent, + } + + package { 'gunicorn': ensure => $gunicorn_ensure } + +} diff --git a/manifests/pip.pp b/manifests/pip.pp new file mode 100644 index 0000000..9762c10 --- /dev/null +++ b/manifests/pip.pp @@ -0,0 +1,64 @@ +# == Define: python::pip +# +# Installs and manages packages from pip. +# +# === Parameters +# +# [*ensure*] +# present|absent. Default: present +# +# [*virtualenv*] +# virtualenv to run pip in. +# +# [*proxy*] +# Proxy server to use for outbound connections. Default: none +# +# === Examples +# +# python::pip { 'flask': +# virtualenv => '/var/www/project1', +# proxy => 'http://proxy.domain.com:3128', +# } +# +# === Authors +# +# Sergey Stankevich +# +define python::pip ( + $virtualenv, + $ensure = present, + $proxy = false +) { + + # Parameter validation + if ! $virtualenv { + fail('python::pip: virtualenv parameter must not be empty') + } + + $proxy_flag = $proxy ? { + false => '', + default => "--proxy=${proxy}", + } + + $grep_regex = $name ? { + /==/ => "^${name}\$", + default => "^${name}==", + } + + case $ensure { + present: { + exec { "pip_install_${name}": + command => "${virtualenv}/bin/pip install ${proxy_flag} ${name}", + unless => "${virtualenv}/bin/pip freeze | grep -i -e ${grep_regex}", + } + } + + default: { + exec { "pip_uninstall_${name}": + command => "echo y | ${virtualenv}/bin/pip uninstall ${proxy_flag} ${name}", + onlyif => "${virtualenv}/bin/pip freeze | grep -i -e ${grep_regex}", + } + } + } + +} diff --git a/manifests/requirements.pp b/manifests/requirements.pp new file mode 100644 index 0000000..e49c86c --- /dev/null +++ b/manifests/requirements.pp @@ -0,0 +1,68 @@ +# == Define: python::requirements +# +# Installs and manages Python packages from requirements file. +# +# === Parameters +# +# [*virtualenv*] +# virtualenv to run pip in. Default: system-wide +# +# [*proxy*] +# Proxy server to use for outbound connections. Default: none +# +# === Examples +# +# python::requirements { '/var/www/project1/requirements.txt': +# virtualenv => '/var/www/project1', +# proxy => 'http://proxy.domain.com:3128', +# } +# +# === Authors +# +# Sergey Stankevich +# +define python::requirements ( + $virtualenv = 'system', + $proxy = false +) { + + $requirements = $name + + $pip_env = $virtualenv ? { + 'system' => '`which pip`', + default => "${virtualenv}/bin/pip", + } + + $proxy_flag = $proxy ? { + false => '', + default => "--proxy=${proxy}", + } + + $req_dir = inline_template('<%= requirements.match(%r!(.+)/.+!)[1] %>') + $req_crc = "${req_dir}/requirements.sha1" + + file { $requirements: + ensure => present, + mode => '0644', + owner => 'root', + group => 'root', + replace => false, + content => '# Puppet will install and/or update pip packages listed here', + } + + # SHA1 checksum to detect changes + exec { "python_requirements_check_${name}": + command => "sha1sum ${requirements} > ${req_crc}", + unless => "sha1sum -c ${req_crc}", + require => File[$requirements], + } + + exec { "python_requirements_update_${name}": + command => "${pip_env} install ${proxy_flag} -Ur ${requirements}", + cwd => $virtualenv, + refreshonly => true, + timeout => 1800, + subscribe => Exec["python_requirements_check_${name}"], + } + +} diff --git a/manifests/virtualenv.pp b/manifests/virtualenv.pp new file mode 100644 index 0000000..a5cf12b --- /dev/null +++ b/manifests/virtualenv.pp @@ -0,0 +1,82 @@ +# == Define: python::virtualenv +# +# Creates Python virtualenv. +# +# === Parameters +# +# [*ensure*] +# present|absent. Default: present +# +# [*version*] +# Python version to use. Default: system default +# +# [*requirements*] +# Path to pip requirements.txt file. Default: none +# +# [*proxy*] +# Proxy server to use for outbound connections. Default: none +# +# === Examples +# +# python::virtualenv { '/var/www/project1': +# ensure => present, +# version => 'system', +# requirements => '/var/www/project1/requirements.txt', +# proxy => 'http://proxy.domain.com:3128', +# } +# +# === Authors +# +# Sergey Stankevich +# +define python::virtualenv ( + $ensure = present, + $version = 'system', + $requirements = false, + $proxy = false +) { + + $venv_dir = $name + + if $ensure == 'present' { + + $python = $version ? { + 'system' => 'python', + default => "python${version}", + } + + $proxy_flag = $proxy ? { + false => '', + default => "--proxy=${proxy}", + } + + exec { "python_virtualenv_${venv_dir}": + command => "mkdir -p ${venv_dir} \ + && export http_proxy=${proxy} \ + && virtualenv -p `which ${python}` ${venv_dir} \ + && ${venv_dir}/bin/pip install ${proxy_flag} --upgrade distribute pip", + creates => $venv_dir, + } + + if $requirements { + Exec["python_virtualenv_${venv_dir}"] + -> Python::Requirements[$requirements] + + python::requirements { $requirements: + virtualenv => $venv_dir, + proxy => $proxy, + } + } + + } elsif $ensure == 'absent' { + + file { $venv_dir: + ensure => absent, + force => true, + recurse => true, + purge => true, + } + + } + +} diff --git a/templates/gunicorn.erb b/templates/gunicorn.erb new file mode 100644 index 0000000..2cf3eed --- /dev/null +++ b/templates/gunicorn.erb @@ -0,0 +1,35 @@ +CONFIG = { +<% if mode == 'django' -%> + 'mode': 'django', +<% else -%> + 'mode': 'wsgi', +<% end -%> +<% if virtualenv -%> + 'environment': { +<% if environment -%> + 'ENVIRONMENT': '<%= environment %>', +<% end -%> + 'PYTHONPATH': '<%= virtualenv %>' + }, +<% end -%> + 'working_dir': '<%= dir %>', + 'user': 'www-data', + 'group': 'www-data', +<% if virtualenv -%> + 'python': '<%= virtualenv %>/bin/python', +<% else -%> + 'python': '/usr/bin/python', +<% end -%> + 'args': ( +<% if !virtualenv and !bind -%> + '--bind=unix:/tmp/gunicorn-<%= name %>.socket', +<% elsif virtualenv and !bind -%> + '--bind=unix:<%= virtualenv %>/<%= name %>.socket', +<% else -%> + '--bind=<%= bind %>', +<% end -%> + '--workers=<%= @processorcount.to_i*2 %>', + '--timeout=30', + 'app:app', + ), +} diff --git a/tests/gunicorn.pp b/tests/gunicorn.pp new file mode 100644 index 0000000..c081fd6 --- /dev/null +++ b/tests/gunicorn.pp @@ -0,0 +1,14 @@ +class { 'python': + version => 'system', + dev => true, + virtualenv => true, +} + +python::gunicorn { 'vhost': + ensure => present, + virtualenv => '/var/www/project1', + mode => 'wsgi', + dir => '/var/www/project1/current', + bind => 'unix:/tmp/gunicorn.socket', + environment => 'prod', +} diff --git a/tests/init.pp b/tests/init.pp new file mode 100644 index 0000000..4c51cc8 --- /dev/null +++ b/tests/init.pp @@ -0,0 +1,5 @@ +class { 'python': + version => 'system', + dev => true, + virtualenv => true, +} diff --git a/tests/pip.pp b/tests/pip.pp new file mode 100644 index 0000000..f0db172 --- /dev/null +++ b/tests/pip.pp @@ -0,0 +1,10 @@ +class { 'python': + version => 'system', + dev => true, + virtualenv => true, +} + +python::pip { 'flask': + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', +} diff --git a/tests/requirements.pp b/tests/requirements.pp new file mode 100644 index 0000000..a57093f --- /dev/null +++ b/tests/requirements.pp @@ -0,0 +1,10 @@ +class { 'python': + version => 'system', + dev => true, + virtualenv => true, +} + +python::requirements { '/var/www/project1/requirements.txt': + virtualenv => '/var/www/project1', + proxy => 'http://proxy.domain.com:3128', +} diff --git a/tests/virtualenv.pp b/tests/virtualenv.pp new file mode 100644 index 0000000..190492c --- /dev/null +++ b/tests/virtualenv.pp @@ -0,0 +1,12 @@ +class { 'python': + version => 'system', + dev => true, + virtualenv => true, +} + +python::virtualenv { '/var/www/project1': + ensure => present, + version => 'system', + requirements => '/var/www/project1/requirements.txt', + proxy => 'http://proxy.domain.com:3128', +} |