summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE.md13
-rw-r--r--Modulefile9
-rw-r--r--README.md101
-rw-r--r--Rakefile8
-rw-r--r--manifests/config.pp24
-rw-r--r--manifests/gunicorn.pp65
-rw-r--r--manifests/init.pp50
-rw-r--r--manifests/install.pp31
-rw-r--r--manifests/pip.pp64
-rw-r--r--manifests/requirements.pp68
-rw-r--r--manifests/virtualenv.pp82
-rw-r--r--templates/gunicorn.erb35
-rw-r--r--tests/gunicorn.pp14
-rw-r--r--tests/init.pp5
-rw-r--r--tests/pip.pp10
-rw-r--r--tests/requirements.pp10
-rw-r--r--tests/virtualenv.pp12
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',
+}