diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d83b2bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +Berksfile.lock +Gemfile.lock +test/gemfiles/*.lock +.kitchen/ +.kitchen.local.yml +test/docker/ +test/ec2/ +coverage/ +pkg/ +.yardoc/ +doc/ diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 0000000..919aef4 --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,3 @@ +--- +#<% require 'poise_boiler' %> +<%= PoiseBoiler.kitchen %> diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..18bf4d0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,68 @@ +sudo: false +cache: bundler +language: ruby +env: + global: + - secure: rz8Ej7Zx5zArH+OwuAsRB8CH0rZVKIAm6nhB29wg73d7FrMv6cWEl5/B2uQqlefqZ1GYosAifhmoW7lVB1S2O9pDPB8wSpr2P9dsaHEupad4jBi6rIufxoCrx3YZIFPcmvL71u2+STi021VQpsXjcwfP6h2i+pVkPXxVr0Ihv3U= +before_install: + - 'if [[ $BUNDLE_GEMFILE == *master.gemfile ]]; then gem update --system; fi' + - gem --version + - gem install bundler + - bundle --version + - 'bundle config --local path ${BUNDLE_PATH:-$(dirname $BUNDLE_GEMFILE)/vendor/bundle}' + - bundle config --local bin $PWD/bin +install: bundle update --jobs=3 --retry=3 +script: + - ./bin/rake travis +matrix: + include: + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.gemfile + - rvm: 2.4.1 + gemfile: test/gemfiles/chef-13.gemfile + - rvm: 2.1.4 + gemfile: test/gemfiles/chef-12.1.gemfile + - rvm: 2.1.4 + gemfile: test/gemfiles/chef-12.2.gemfile + - rvm: 2.1.4 + gemfile: test/gemfiles/chef-12.3.gemfile + - rvm: 2.1.6 + gemfile: test/gemfiles/chef-12.4.gemfile + - rvm: 2.1.6 + gemfile: test/gemfiles/chef-12.5.gemfile + - rvm: 2.1.6 + gemfile: test/gemfiles/chef-12.6.gemfile + - rvm: 2.1.6 + gemfile: test/gemfiles/chef-12.7.gemfile + - rvm: 2.1.6 + gemfile: test/gemfiles/chef-12.8.gemfile + - rvm: 2.1.8 + gemfile: test/gemfiles/chef-12.9.gemfile + - rvm: 2.1.8 + gemfile: test/gemfiles/chef-12.10.gemfile + - rvm: 2.1.8 + gemfile: test/gemfiles/chef-12.11.gemfile + - rvm: 2.1.8 + gemfile: test/gemfiles/chef-12.12.gemfile + - rvm: 2.1.9 + gemfile: test/gemfiles/chef-12.13.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.14.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.15.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.16.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.17.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.18.gemfile + - rvm: 2.3.1 + gemfile: test/gemfiles/chef-12.19.gemfile + - rvm: 2.4.1 + gemfile: test/gemfiles/chef-13.0.gemfile + - rvm: 2.4.1 + gemfile: test/gemfiles/chef-13.1.gemfile + - rvm: 2.4.1 + gemfile: test/gemfiles/chef-13.2.gemfile + - rvm: 2.4.1 + gemfile: test/gemfiles/master.gemfile diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..be7cdef --- /dev/null +++ b/.yardopts @@ -0,0 +1,7 @@ +--plugin classmethods +--embed-mixin ClassMethods +--hide-api private +--markup markdown +--hide-void-return +--tag provides:Provides +--tag action:Actions diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..9cb8344 --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,7 @@ +{ + "generator-poise": { + "created": true, + "name": "poise-application-python", + "cookbookName": "application_python" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ab75783..f2b213a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,68 +1,71 @@ -application_python Cookbok CHANGELOG -==================================== -This file is used to list changes made in each version of the application_python cookbook. +# Application_Python Changelog +## v4.0.0 -v2.0.2 ------- -### Bug -- **[COOK-3432](https://tickets.opscode.com/browse/COOK-3432)** - Use `Chef::DSL::IncludeRecipe` because `Chef::Mixin::LanguageIncludeRecipe` is deprecated +* Massive rewrite on top of newer Chef patterns. See the 4.0 README for details. -v2.0.0 ------- -### Bug -- [COOK-3306]: Multiple Memory Leaks in Application Cookbook +## v3.0.0 -v1.2.4 ------- -### Bug +* Drop support for Chef 10. +* [COOK-2212](https://tickets.opscode.com/browse/COOK-2212) - autostart when server reboots. +* [COOK-3432](https://tickets.opscode.com/browse/COOK-3432) - Use `Chef::DSL::IncludeRecipe` because `Chef::Mixin::LanguageIncludeRecipe` is deprecated. -- [COOK-2747]: celerycam configuration is not suitable for multiple node celery installation -- [COOK-2766]: pip does not use `deploy_key` in django ressource of `application_python` +## v2.0.4 -v1.2.2 ------- -### Bug -- [COOK-2796]: celery provider tries to case switch on 'queue' parameter instead of 'queues' parameter +* Revert changes that broke backwards compatibility. -v1.2.0 ------- -### Improvement -- [COOK-2611]: Celery LWRP should configure which queues a celeryd worker binds to +## v2.0.2 -### Bug +* **This release did not follow semver and was reverted in 2.0.4!** +* [COOK-3432](https://tickets.opscode.com/browse/COOK-3432) - Use `Chef::DSL::IncludeRecipe` because `Chef::Mixin::LanguageIncludeRecipe` is deprecated. -- [COOK-2599]: gunicorn provider fails if no `node['cpu']['total']` +## v2.0.0 + +* [COOK-3306]: Multiple Memory Leaks in Application Cookbook. + +## v1.2.4 + +* [COOK-2747]: celerycam configuration is not suitable for multiple node celery installation. +* [COOK-2766]: pip does not use `deploy_key` in django ressource of `application_python`. + +## v1.2.2 + +* [COOK-2796]: celery provider tries to case switch on 'queue' parameter instead of 'queues' parameter. + +## v1.2.0 + +* [COOK-2611]: Celery LWRP should configure which queues a celeryd worker binds to. +* [COOK-2599]: gunicorn provider fails if no `node['cpu']['total']`. + +## v1.1.0 -v1.1.0 ------- * [COOK-2330] - celeryconfig.py.erb tries to use non-existant String#upper method * [COOK-2337] - It should be possible to pass environment variables through to gunicorn and celery supervisor configs -* [COOK-2403] - cookbook attribute expects argument to be a string -* [COOK-2453] - application_python should allow the working directory of gunicorn processes to be set via an attribute -* [COOK-2475] - celerybeat supervisor process is unnecessarily configured -* [COOK-2484] - virtualenv and requirements are installed as root instead of uid/gid specified by application properties +* [COOK-2403] - cookbook attribute expects argument to be a string. +* [COOK-2453] - application_python should allow the working directory of gunicorn processes to be set via an attribute. +* [COOK-2475] - celerybeat supervisor process is unnecessarily configured. +* [COOK-2484] - virtualenv and requirements are installed as root instead of uid/gid specified by application properties. + +## v1.0.8 -v1.0.8 ------- * [COOK-2175] - Template cookbook attribute expecting a stringg getting symbol instead. -v1.0.6 ------- -* [COOK-2122] - pip was incorrectly using -E syntax -* [COOK-2147] - django sub-resource searched wrong directory for requirements.txt +## v1.0.6 + +* [COOK-2122] - pip was incorrectly using -E syntax. +* [COOK-2147] - django sub-resource searched wrong directory for requirements.txt. + +## v1.0.4 + +* [COOK-2042] - gunicorn LWRP support for virtualenv, deps. + +## v1.0.2 -v1.0.4 ------- -* [COOK-2042] - gunicorn LWRP support for virtualenv, deps +* [COOK-1420] - template resource source cookbook is wrong. +* [COOK-1421] - pip using old -E syntax. +* [COOK-1422] - syncdb using --migrate option. +* [COOK-1477] - pip requirements.txt and editable package support. -v1.0.2 ------- -* [COOK-1420] - template resource source cookbook is wrong -* [COOK-1421] - pip using old -E syntax -* [COOK-1422] - syncdb using --migrate option -* [COOK-1477] - pip requirements.txt and editable package support +## v1.0.0 -v1.0.0 ------- * [COOK-1246] - Initial release - relates to COOK-634. diff --git a/CONTRIBUTING b/CONTRIBUTING deleted file mode 100644 index 89ac873..0000000 --- a/CONTRIBUTING +++ /dev/null @@ -1,29 +0,0 @@ -If you would like to contribute, please open a ticket in JIRA: - -* http://tickets.opscode.com - -Create the ticket in the COOK project and use the cookbook name as the -component. - -For all code contributions, we ask that contributors sign a -contributor license agreement (CLA). Instructions may be found here: - -* http://wiki.opscode.com/display/chef/How+to+Contribute - -When contributing changes to individual cookbooks, please do not -modify the version number in the metadata.rb. Also please do not -update the CHANGELOG.md for a new version. Not all changes to a -cookbook may be merged and released in the same versions. Opscode will -handle the version updates during the release process. You are welcome -to correct typos or otherwise make updates to documentation in the -README. - -If a contribution adds new platforms or platform versions, indicate -such in the body of the commit message(s), and update the relevant -COOK ticket. When writing commit messages, it is helpful for others if -you indicate the COOK ticket. For example: - - git commit -m '[COOK-1041] Updated pool resource to correctly delete.' - -In the ticket itself, it is also helpful if you include log output of -a successful Chef run, but this is not absolutely required. diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..200b1cb --- /dev/null +++ b/Gemfile @@ -0,0 +1,39 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +source 'https://rubygems.org/' + +gemspec path: File.expand_path('..', __FILE__) + +def dev_gem(name, path: File.join('..', name), github: nil) + path = File.expand_path(File.join('..', path), __FILE__) + if File.exist?(path) + gem name, path: path + elsif github + gem name, git: "https://github.com/#{github}.git" + end +end + +dev_gem 'halite' +dev_gem 'poise' +dev_gem 'poise-application', path: '../application' +dev_gem 'poise-application-git', path: '../application_git' +dev_gem 'poise-archive' +dev_gem 'poise-boiler' +dev_gem 'poise-languages' +dev_gem 'poise-profiler' +dev_gem 'poise-python' +dev_gem 'poise-service' diff --git a/README.md b/README.md index 4f12237..74ed6f7 100644 --- a/README.md +++ b/README.md @@ -1,183 +1,335 @@ -application_python Cookbook -=========================== -This cookbook is designed to be able to describe and deploy Python web applications. Currently supported: - -- plain python web applications -- Django -- Green Unicorn -- Celery - -Note that this cookbook provides the Python-specific bindings for the `application` cookbook; you will find general documentation in that cookbook. - -Other application stacks may be supported at a later date. - - -Requirements ------------- -Chef 0.10.0 or higher required (for Chef environment use). - -The following Opscode cookbooks are dependencies: - -- application -- python -- gunicorn -- supervisor - - -Resources/Providers -------------------- -The LWRPs provided by this cookbook are not meant to be used by themselves; make sure you are familiar with the `application` cookbook before proceeding. - - -### django -The `django` sub-resource LWRP deals with deploying Django webapps from an SCM repository. It uses the `deploy_revision` LWRP to perform the bulk of its tasks, and many concepts and parameters map directly to it. Check the documentation for `deploy_revision` for more information. - -A new virtualenv will be created for the application in "#{path}/shared/env"; pip package will be installed in that virtualenv. - -#### Attribute Parameters -- packages: an Array of pip packages to install -- requirements: the relative path to a requirements file. If not specified the provider will look for one in the project root, named either "requirements/#{chef_environment}.txt" or "requirements.txt" -- database\_master\_role: if a role name is provided, a Chef search will be run to find a node with than role in the same environment as the current role. If a node is found, its IP address will be used when rendering the context file, but see the "Database block parameters" section below -- local\_settings\_file: the name of the local settings file to be generated by template. Defaults to "local_settings.py" -- settings\_template: the name of template that will be rendered to create the local settings file; if specified it will be looked up in the application cookbook. Defaults to "settings.py.erb" from this cookbook -- settings: a Hash of additional settings that will be made available to the template -- database: a block containing additional parameters for configuring the database connection -- legacy\_database\_settings: if true, the default settings template will generate legacy database config variables. Defaults to false -- debug: used by the default settings template to control debugging. Defaults to false -- collectstatic: controls the behavior of the `staticfiles` app. If true, if will invoke manage.py with `collectstatic --noinput`; you can also pass a String with an explicit command (see Usage below). Defaults to false - -#### Database block parameters - -The database block can accept any method, which will result in an entry being created in the `@database` Hash which is passed to the context template. See Usage below for more information. - -### gunicorn -The `gunicorn` sub-resource LWRP configures Green Unicorn to run the application. - -If used with a Django application, it will install gunicorn into the same virtualenv and run it with `manage.py run_gunicorn`. For other applications, gunicorn will be run with `gunicorn #{app_module}`. - -#### Attribute Parameters - -- app_module: mandatory. If set to :django, gunicorn will be configured to run a Django application; if set to another String or Symbol, it will be used to build the gunicorn base command. -- settings\_template: the template to render to create the `gunicorn_config.py` file; if specified it will be looked up in the application cookbook. Defaults to "se.py.erb" from the `gunicorn` cookbook -- host: passed to the `gunicorn_config` LWRP -- port: passed to the `gunicorn_config` LWRP -- backlog: passed to the `gunicorn_config` LWRP -- workers: passed to the `gunicorn_config` LWRP -- worker_class: passed to the `gunicorn_config` LWRP -- worker_connections: passed to the `gunicorn_config` LWRP -- max_requests: passed to the `gunicorn_config` LWRP -- timeout: passed to the `gunicorn_config` LWRP -- keepalive: passed to the `gunicorn_config` LWRP -- debug: passed to the `gunicorn_config` LWRP -- trace: passed to the `gunicorn_config` LWRP -- preload_app: passed to the `gunicorn_config` LWRP -- daemon: passed to the `gunicorn_config` LWRP -- pidfile: passed to the `gunicorn_config` LWRP -- umask: passed to the `gunicorn_config` LWRP -- logfile: passed to the `gunicorn_config` LWRP -- loglevel: passed to the `gunicorn_config` LWRP -- proc_name: passed to the `gunicorn_config` LWRP -- environment: hash of environment variables passed to `supervisor_service` -- autostart: passed to `supervisor_service`. - - -### celery -The `celery` sub resource LWRP configures the application to use Celery. - -#### Attribute Parameters -- config: passed to `supervisor_service` for `CELERY_CONFIG_MODULE`. -- template: name of the template to use, default `celeryconfig.py.erb`. -- django: use this if celery is for a django application, see - `celerycam` below. -- celeryd: adds celeryd to processes managed for the application by `supervisor`. -- celerybeat: adds celerybeat to processes managed for the application - by `supervisor`. -- celerycam: adds celerycam to the processes managed for the - application by `supervisor` if `django` is true for celery - sub-resource, or celeryev with the class specified with `camera_class`. -- camera_class: class passed into celeryev for the processes managed - for the application by supervisor. -- environment: hash of environment variables passed to the `supervisor_service`. - - -Usage ------ -A sample application that needs a database connection: +# Application_Python Cookbook -```ruby -application "packaginator" do - path "/srv/packaginator" - owner "nobody" - group "nogroup" - repository "https://github.com/coderanger/packaginator.git" - revision "master" - migrate true - packages ["libpq-dev", "git-core", "mercurial"] +[![Build Status](https://img.shields.io/travis/poise/application_python.svg)](https://travis-ci.org/poise/application_python) +[![Gem Version](https://img.shields.io/gem/v/poise-application-python.svg)](https://rubygems.org/gems/poise-application-python) +[![Cookbook Version](https://img.shields.io/cookbook/v/application_python.svg)](https://supermarket.chef.io/cookbooks/application_python) +[![Coverage](https://img.shields.io/codecov/c/github/poise/application_python.svg)](https://codecov.io/github/poise/application_python) +[![Gemnasium](https://img.shields.io/gemnasium/poise/application_python.svg)](https://gemnasium.com/poise/application_python) +[![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +A [Chef](https://www.chef.io/) cookbook to deploy Python applications. + +## Quick Start + +To deploy a Django application from git: +```ruby +application '/srv/myapp' do + git 'https://github.com/example/myapp.git' + virtualenv + pip_requirements django do - packages ["redis"] - requirements "requirements/mkii.txt" - settings_template "settings.py.erb" - debug true - collectstatic "build_static --noinput" - database do - database "packaginator" - engine "postgresql_psycopg2" - username "packaginator" - password "awesome_password" + database 'sqlite:///test_django.db' + secret_key 'd78fe08df56c9' + migrate true + end + gunicorn do + port 8000 + end +end +``` + +## Requirements + +Chef 12.1 or newer is required. + +## Resources + +### `application_celery_beat` + +The `application_celery_beat` resource creates a service for the `celery beat` +process. + +```ruby +application '/srv/myapp' do + celery_beat do + app_module 'myapp.tasks' + end +end +``` + +#### Actions + +* `:enable` – Create, enable and start the service. *(default)* +* `:disable` – Stop, disable, and destroy the service. +* `:start` – Start the service. +* `:stop` – Stop the service. +* `:restart` – Stop and then start the service. +* `:reload` – Send the configured reload signal to the service. + +#### Properties + +* `path` – Base path for the application. *(name attribute)* +* `app_module` – Celery application module. *(default: auto-detect)* +* `service_name` – Name of the service to create. *(default: auto-detect)* +# `user` – User to run the service as. *(default: application owner)* + +### `application_celery_config` + +The `application_celery_config` creates a `celeryconfig.py` configuration file. + +```ruby +application '/srv/myapp' do + celery_config do + options do + broker_url 'amqp://' end - database_master_role "packaginator_database_master" end end ``` -You can invoke any method on the database block: +#### Actions + +* `:deploy` – Create the configuration file. *(default)* + +#### Properties + +* `path` – Path to write the configuration file to. If given as a directory, + create `path/celeryconfig.py`. *(name attribute)* +* `options` – Hash or block of options to set in the configuration file. + +### `application_celery_worker` + +The `application_celery_worker` resource creates a service for the +`celery worker` process. ```ruby -application "my-app" do - path "/srv/packaginator" - repository "..." - revision "..." +application '/srv/myapp' do + celery_worker do + app_module 'myapp.tasks' + end +end +``` + +#### Actions + +* `:enable` – Create, enable and start the service. *(default)* +* `:disable` – Stop, disable, and destroy the service. +* `:start` – Start the service. +* `:stop` – Stop the service. +* `:restart` – Stop and then start the service. +* `:reload` – Send the configured reload signal to the service. + +#### Properties + +* `path` – Base path for the application. *(name attribute)* +* `app_module` – Celery application module. *(default: auto-detect)* +* `worker_pool` – The Pool implementation used by the Celery worker (gevent,eventlet or prefork). *(default: prefork)* +* `service_name` – Name of the service to create. *(default: auto-detect)* +# `user` – User to run the service as. *(default: application owner)* + +### `application_django` + +The `application_django` resource creates configuration files and runs commands +for a Django application deployment. +```ruby +application '/srv/myapp' do django do - database_master_role "packaginator_database_master" - database do - database 'name' - quorum 2 - replicas %w[Huey Dewey Louie] - end + database 'sqlite:///test_django.db' + migrate true end end ``` -The corresponding entries will be passed to the context template: +#### Actions + +* `:deploy` – Create config files and run required deployments steps. *(default)* + +#### Properties + +* `path` – Base path for the application. *(name attribute)* +* `allowed_hosts` – Value for `ALLOWED_HOSTS` in the Django settings. + *(default: [])* +* `collectstatic` – Run `manage.py collectstatic` during deployment. + *(default: true)* +* `database` – Database settings for the default connection. See [the database + section below](#database-parameters) for more information. *(option collector)* +* `debug` – Enable debug mode for Django. *(default: false)* +* `local_settings` – A [Poise template property](https://github.com/poise/poise#template-content) + for the content of the local settings configuration file. +* `local_settings_path` – Path to write local settings to. If given as a + relative path, will be expanded against path. Set to false to disable writing + local settings. *(default: local_settings.py next to settings_module)* +* `migrate` – Run `manage.py migrate` during deployment. *(default: false)* +* `manage_path` – Path to `manage.py`. *(default: auto-detect)* +* `secret_key` – Value for `SECRET_KEY` in the Django settings. If unset, no + key is added to the local settings. +* `settings_module` – Django settings module in dotted notation. + *(default: auto-detect)* +* `syncdb` – Run `manage.py syncdb` during deployment. *(default: false)* +* `wsgi_module` – WSGI application module in dotted notation. + *(default: auto-detect)* + +#### Database Parameters + +The database parameters can be set in three ways: URL, hash, and block. + +If you have a single URL for the parameters, you can pass it directly to +`database`: -```erb -<%= @database['quorum'] %> -<%= @database['replicas'].join(',') %> +```ruby +django do + database 'postgres://myuser@dbhost/myapp' +end ``` +Passing a single URL will also set the `$DATABASE_URL` environment variable +automatically for compatibility with Heroku-based applications. -License & Authors ------------------ -- Author:: Adam Jacob () -- Author:: Andrea Campi () -- Author:: Joshua Timberman () -- Author:: Noah Kantrowitz () -- Author:: Seth Chisamore () +As with other option collector resources, you can pass individual settings as +either a hash or block: -```text -Copyright 2009-2013, Opscode, Inc. +```ruby +django do + database do + engine 'postgres' + user 'myuser' + host 'dbhost' + name 'myapp' + end +end + +django do + database({ + engine: 'postgres', + user: 'myuser', + host: 'dbhost', + name: 'myapp', + }) +end +``` + +### `application_gunicorn` + +The `application_gunicorn` resource creates a service for the +[Gunicorn](http://gunicorn.org/) application server. + +```ruby +application '/srv/myapp' do + gunicorn do + port 8000 + preload_app true + end +end +``` + +#### Actions + +* `:enable` – Create, enable and start the service. *(default)* +* `:disable` – Stop, disable, and destroy the service. +* `:start` – Start the service. +* `:stop` – Stop the service. +* `:restart` – Stop and then start the service. +* `:reload` – Send the configured reload signal to the service. + +#### Properties + +* `path` – Base path for the application. *(name attribute)* +* `app_module` – WSGI module to run as an application. *(default: auto-detect)* +* `bind` – One or more addresses/ports to bind to. +* `config` – Path to a Guncorn configuration file. +* `preload_app` – Enable code preloading. *(default: false)* +* `port` – Port to listen on. Alias for `bind("0.0.0.0:#{port}")`. +* `service_name` – Name of the service to create. *(default: auto-detect)* +* `user` – User to run the service as. *(default: application owner)* +* `version` – Version of Gunicorn to install. If set to true, use the latest + version. If set to false, do not install Gunicorn. *(default: true)* + +### `application_pip_requirements` + +The `application_pip_requirements` resource installs Python packages based on a +`requirements.txt` file. + +```ruby +application '/srv/myapp' do + pip_requirements +end +``` + +All actions and properties are the same as the [`pip_requirements` resource](https://github.com/poise/poise-python#pip_requirements). + +### `application_python` + +The `application_python` resource installs a Python runtime for the deployment. + +```ruby +application '/srv/myapp' do + python '2.7' +end +``` + +All actions and properties are the same as the [`python_runtime` resource](https://github.com/poise/poise-python#python_runtime). + +### `application_python_execute` + +The `application_python_execute` resource runs Python commands for the deployment. + +```ruby +application '/srv/myapp' do + python_execute 'setup.py install' +end +``` + +All actions and properties are the same as the [`python_execute` resource](https://github.com/poise/poise-python#python_execute), +except that the `cwd`, `environment`, `group`, and `user` properties default to +the application-level data if not specified. + +### `application_python_package` + +The `application_python_package` resource installs Python packages for the deployment. + +```ruby +application '/srv/myapp' do + python_package 'requests' +end +``` + +All actions and properties are the same as the [`python_package` resource](https://github.com/poise/poise-python#python_package), +except that the `group` and `user` properties default to the application-level +data if not specified. + +### `application_virtualenv` + +The `application_virtualenv` resource creates a Python virtualenv for the +deployment. + +```ruby +application '/srv/myapp' do + virtualenv +end +``` + +If no path property is given, the default is to create a `.env/` inside the +application deployment path. + +All actions and properties are the same as the [`python_virtualenv` resource](https://github.com/poise/poise-python#python_virtualenv). + +## Examples + +Some test recipes are available as examples for common application frameworks: + +* [Flask](https://github.com/poise/application_python/blob/master/test/cookbook/recipes/flask.rb) +* [Django](https://github.com/poise/application_python/blob/master/test/cookbook/recipes/django.rb) + +## Sponsors + +Development sponsored by [Chef Software](https://www.chef.io/), [Symonds & Son](http://symondsandson.com/), and [Orion](https://www.orionlabs.co/). + +The Poise test server infrastructure is sponsored by [Rackspace](https://rackspace.com/). + +## License + +Copyright 2015-2017, Noah Kantrowitz 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/Rakefile b/Rakefile new file mode 100644 index 0000000..d2b12d0 --- /dev/null +++ b/Rakefile @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_boiler/rakefile' diff --git a/SUPPORTERS.md b/SUPPORTERS.md new file mode 100644 index 0000000..fa5a652 --- /dev/null +++ b/SUPPORTERS.md @@ -0,0 +1,81 @@ +# Supporters + +This cookbook wouldn't have been possible without the generous support of my +Kickstarter backers. Thanks so much to every one of you! + +* @kcunning +* Alberto Lorenzo Pulido +* Alex Gaynor +* Alexander Myasnikov +* Brooke Schreier Ganz +* Bryan McLellan +* Charles Johnson +* Chef Software, Inc. +* Chris Adams +* Christopher Petrilli +* David Thornton +* Derek Murawsky +* Fast Robot, LLC +* Fatty McAwesome Pants (Kate Heddleston) +* Greg Albrecht +* JD Harrington +* Jake Plimack +* Jason Cook +* Jeff Byrnes +* Jeff Forcier +* Jeff Lindsay +* Jennifer Davis +* John Fitch +* Jon Stacks +* Joshua SS Miller +* Julian Dunn +* Julien Phalip +* Kennon Kwok +* Kevin Duane +* Krzysztof Wilczynski +* Linda Goldstein +* Manny Toledo +* Marco A Morales +* Marcus Morris +* Mark Luntzel +* Martin B. Smith +* Mathieu Sauve-Frankel +* Matt Good +* Matt Juszczak +* Matt Ray +* Matt Stratton +* Michael Burns +* Miguel Landaeta +* Mike Schueler +* Nathan L Smith +* Nathen Harvey +* Paul Welch +* Peter Kropf +* Phil Mocek +* Practice Fusion Inc +* Ranjib +* Richard Jones +* Robert J. Berger +* Robin Levin +* Roland Moriz +* Ruben Orduz +* Russell Keith-Magee +* Ryan Hass +* Sam Clements +* Sean OMeara +* Seva Feldman +* Soo Choi +* Steven Danna +* Symonds & Son +* Todd Michael Bushnell +* Tracy Osborn +* Troy Ready +* Tyler Ball +* Vicky Tuite +* Ying Li +* Yvo van Doorn +* Zac Stevens +* lvh +* oolongtea +* smeuser +* tmonk42 diff --git a/chef/templates/celeryconfig.py.erb b/chef/templates/celeryconfig.py.erb new file mode 100644 index 0000000..3e47b9a --- /dev/null +++ b/chef/templates/celeryconfig.py.erb @@ -0,0 +1,5 @@ +# Generated by Chef for <%= @new_resource.to_s %> + +<%- @new_resource.options.each do |key, value| -%> +<%= key.upcase %> = <%= PoisePython::Utils.to_python(value) %> +<%- end -%> diff --git a/chef/templates/settings.py.erb b/chef/templates/settings.py.erb new file mode 100644 index 0000000..88ee90f --- /dev/null +++ b/chef/templates/settings.py.erb @@ -0,0 +1,13 @@ +# Generated by Chef for <%= @new_resource.to_s %> + +<%- unless @allowed_hosts.empty? -%> +ALLOWED_HOSTS = <%= PoisePython::Utils.to_python(@allowed_hosts) %> + +<%- end -%> +DEBUG = <%= PoisePython::Utils.to_python(@debug) %> + +DATABASES = <%= PoisePython::Utils.to_python(@databases) %> +<%- if @secret_key -%> + +SECRET_KEY = <%= PoisePython::Utils.to_python(@secret_key) %> +<%- end -%> diff --git a/examples/recipes-packaginator.rb b/examples/recipes-packaginator.rb deleted file mode 100644 index 7fa2bee..0000000 --- a/examples/recipes-packaginator.rb +++ /dev/null @@ -1,48 +0,0 @@ -application "packaginator" do - path "/srv/packaginator" - owner "nobody" - group "nogroup" - repository "https://github.com/coderanger/packaginator.git" - revision "master" - migrate true - packages ["libpq-dev", "git-core", "mercurial"] - - django do - packages ["redis"] - requirements "requirements/mkii.txt" - settings_template "settings.py.erb" - debug true - collectstatic "build_static --noinput" - database do - database "packaginator" - engine "postgresql_psycopg2" - username "packaginator" - password "awesome_password" - end - database_master_role "packaginator_database_master" - end - - gunicorn do - only_if { node['roles'].include? 'packaginator_application_server' } - app_module :django - port 8080 - end - - celery do - only_if { node['roles'].include? 'packaginator_application_server' } - config "celery_settings.py" - django true - celerybeat true - celerycam true - broker do - transport "redis" - end - end - - nginx_load_balancer do - only_if { node['roles'].include? 'packaginator_load_balancer' } - application_port 8080 - static_files "/site_media" => "site_media" - end - -end diff --git a/lib/poise_application_python.rb b/lib/poise_application_python.rb new file mode 100644 index 0000000..028595f --- /dev/null +++ b/lib/poise_application_python.rb @@ -0,0 +1,23 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + + +module PoiseApplicationPython + autoload :AppMixin, 'poise_application_python/app_mixin' + autoload :Error, 'poise_application_python/error' + autoload :Resources, 'poise_application_python/resources' + autoload :VERSION, 'poise_application_python/version' +end diff --git a/lib/poise_application_python/app_mixin.rb b/lib/poise_application_python/app_mixin.rb new file mode 100644 index 0000000..048ce6e --- /dev/null +++ b/lib/poise_application_python/app_mixin.rb @@ -0,0 +1,67 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise/backports' +require 'poise/utils' +require 'poise_application/app_mixin' +require 'poise_python/python_command_mixin' + + +module PoiseApplicationPython + # A helper mixin for Python application resources and providers. + # + # @since 4.0.0 + module AppMixin + include Poise::Utils::ResourceProviderMixin + + # A helper mixin for Python application resources. + module Resource + include PoiseApplication::AppMixin::Resource + include PoisePython::PythonCommandMixin::Resource + + # @!attribute parent_python + # Override the #parent_python from PythonCommandMixin to grok the + # application level parent as a default value. + # @return [PoisePython::Resources::PythonRuntime::Resource, nil] + parent_attribute(:python, type: :python_runtime, optional: true, default: lazy { app_state_python.equal?(self) ? nil : app_state_python }) + + # @attribute app_state_python + # The application-level Python parent. + # @return [PoisePython::Resources::PythonRuntime::Resource, nil] + def app_state_python(python=Poise::NOT_PASSED) + unless python == Poise::NOT_PASSED + app_state[:python] = python + end + app_state[:python] + end + + # A merged hash of environment variables for both the application state + # and parent python. + # + # @return [Hash] + def app_state_environment_python + env = app_state_environment + env = env.merge(parent_python.python_environment) if parent_python + env + end + end + + # A helper mixin for Python application providers. + module Provider + include PoiseApplication::AppMixin::Provider + end + end +end diff --git a/lib/poise_application_python/cheftie.rb b/lib/poise_application_python/cheftie.rb new file mode 100644 index 0000000..ad04a51 --- /dev/null +++ b/lib/poise_application_python/cheftie.rb @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_application_python/resources' diff --git a/lib/poise_application_python/error.rb b/lib/poise_application_python/error.rb new file mode 100644 index 0000000..dfdacd0 --- /dev/null +++ b/lib/poise_application_python/error.rb @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_application/error' + +module PoiseApplicationPython + # Base exception class for poise-application-python errors. + # + # @since 4.0.0 + class Error < PoiseApplication::Error + end +end diff --git a/lib/poise_application_python/resources.rb b/lib/poise_application_python/resources.rb new file mode 100644 index 0000000..a7b8fa3 --- /dev/null +++ b/lib/poise_application_python/resources.rb @@ -0,0 +1,26 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_application_python/resources/celery_beat' +require 'poise_application_python/resources/celery_config' +require 'poise_application_python/resources/celery_worker' +require 'poise_application_python/resources/django' +require 'poise_application_python/resources/gunicorn' +require 'poise_application_python/resources/pip_requirements' +require 'poise_application_python/resources/python' +require 'poise_application_python/resources/python_execute' +require 'poise_application_python/resources/python_package' +require 'poise_application_python/resources/virtualenv' diff --git a/lib/poise_application_python/resources/celery_beat.rb b/lib/poise_application_python/resources/celery_beat.rb new file mode 100644 index 0000000..e5664ca --- /dev/null +++ b/lib/poise_application_python/resources/celery_beat.rb @@ -0,0 +1,43 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_application_python/resources/celery_worker' + + +module PoiseApplicationPython + module Resources + # (see CeleryBeat::Resource) + # @since 4.0.0 + module CeleryBeat + class Resource < PoiseApplicationPython::Resources::CeleryWorker::Resource + provides(:application_celery_beat) + end + + class Provider < PoiseApplicationPython::Resources::CeleryWorker::Provider + provides(:application_celery_beat) + + private + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + resource.command("#{new_resource.python} -m celery --app=#{new_resource.app_module} beat") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/celery_config.rb b/lib/poise_application_python/resources/celery_config.rb new file mode 100644 index 0000000..f4a835e --- /dev/null +++ b/lib/poise_application_python/resources/celery_config.rb @@ -0,0 +1,109 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'uri' + +require 'chef/provider' +require 'chef/resource' +require 'poise' +require 'poise_application' +require 'poise_python' + +require 'poise_application_python/app_mixin' +require 'poise_application_python/error' + + +module PoiseApplicationPython + module Resources + # (see CeleryConfig::Resource) + # @since 4.0.0 + module CeleryConfig + # An `application_celery_config` resource to configure Celery workers. + # + # @since 4.0.0 + # @provides application_celery_config + # @action deploy + # @example + # application '/srv/myapp' do + # git '...' + # pip_requirements + # celery_config do + # options do + # broker_url '...' + # end + # end + # celeryd + # end + class Resource < Chef::Resource + include PoiseApplicationPython::AppMixin + provides(:application_celery_config) + actions(:deploy) + + attribute('', template: true, default_source: 'celeryconfig.py.erb') + # @!attribute group + # Owner for the Django application, defaults to application group. + # @return [String] + attribute(:group, kind_of: String, default: lazy { parent && parent.group }) + # @!attribute owner + # Owner for the Django application, defaults to application owner. + # @return [String] + attribute(:owner, kind_of: String, default: lazy { parent && parent.owner }) + attribute(:path, kind_of: String, default: lazy { default_path }) + + private + + def default_path + if ::File.directory?(name) + ::File.join(name, 'celeryconfig.py') + else + name + end + end + end + + # Provider for `application_celery_config`. + # + # @since 4.0.0 + # @see Resource + # @provides application_celery_config + class Provider < Chef::Provider + include PoiseApplicationPython::AppMixin + provides(:application_celery_config) + + # `deploy` action for `application_celery_config`. Writes config file. + # + # @return [void] + def action_deploy + notifying_block do + write_config + end + end + + private + + def write_config + file new_resource.path do + content new_resource.content + mode '640' + owner new_resource.owner + group new_resource.group + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/celery_worker.rb b/lib/poise_application_python/resources/celery_worker.rb new file mode 100644 index 0000000..520a446 --- /dev/null +++ b/lib/poise_application_python/resources/celery_worker.rb @@ -0,0 +1,78 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'chef/provider' +require 'chef/resource' + +require 'poise_application_python/service_mixin' + + +module PoiseApplicationPython + module Resources + # (see CeleryWorker::Resource) + # @since 4.0.0 + module CeleryWorker + class Resource < Chef::Resource + include PoiseApplicationPython::ServiceMixin + provides(:application_celery_worker) + + attribute(:worker_pool, kind_of: [String, NilClass], default: "prefork") + attribute(:app_module, kind_of: [String, NilClass], default: lazy { default_app_module }) + + private + + # Compute the default application module to pass to gunicorn. This + # checks the app state and then looks for commonly used filenames. + # Raises an exception if no default can be found. + # + # @return [String] + def default_app_module + # If set in app_state, use that. + return app_state[:python_celery_module] if app_state[:python_celery_module] + # If a Django settings module is set, use everything by the last + # dotted component of it. to_s handles nil since that won't match. + return $1 if app_state_environment[:DJANGO_SETTINGS_MODULE].to_s =~ /^(.+?)\.[^.]+$/ + files = Dir.exist?(path) ? Dir.entries(path) : [] + # Try to find a known filename. + candidate_file = %w{tasks.py task.py celery.py main.py app.py application.py}.find {|file| files.include?(file) } + # Try the first Python file. Do I really want this? + candidate_file ||= files.find {|file| file.end_with?('.py') } + if candidate_file + ::File.basename(candidate_file, '.py') + else + nil + end + end + + end + + class Provider < Chef::Provider + include PoiseApplicationPython::ServiceMixin + provides(:application_celery_worker) + + private + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + raise PoiseApplicationPython::Error.new("Unable to determine app module for #{new_resource}") unless new_resource.app_module + resource.command("#{new_resource.python} -m celery --app=#{new_resource.app_module} -P #{new_resource.worker_pool} worker") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/django.rb b/lib/poise_application_python/resources/django.rb new file mode 100644 index 0000000..61dd5d7 --- /dev/null +++ b/lib/poise_application_python/resources/django.rb @@ -0,0 +1,355 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'uri' + +require 'chef/provider' +require 'chef/resource' +require 'poise' +require 'poise_application' +require 'poise_python' + +require 'poise_application_python/app_mixin' +require 'poise_application_python/error' + + +module PoiseApplicationPython + module Resources + # (see Django::Resource) + # @since 4.0.0 + module Django + # Aliases for Django database engine names. Based on https://github.com/kennethreitz/dj-database-url/blob/master/dj_database_url.py + # Copyright 2014-2017, Kenneth Reitz. + ENGINE_ALIASES = { + 'postgres' => 'django.db.backends.postgresql_psycopg2', + 'postgresql' => 'django.db.backends.postgresql_psycopg2', + 'pgsql' => 'django.db.backends.postgresql_psycopg2', + 'postgis' => 'django.contrib.gis.db.backends.postgis', + 'mysql2' => 'django.db.backends.mysql', + 'mysqlgis' => 'django.contrib.gis.db.backends.mysql', + 'spatialite' => 'django.contrib.gis.db.backends.spatialite', + 'sqlite' => 'django.db.backends.sqlite3', + } + + # An `application_django` resource to configure Django applications. + # + # @since 4.0.0 + # @provides application_django + # @action deploy + # @example + # application '/srv/myapp' do + # git '...' + # pip_requirements + # django do + # database do + # host node['db_host'] + # end + # end + # gunicorn do + # port 8080 + # end + # end + class Resource < Chef::Resource + include PoiseApplicationPython::AppMixin + provides(:application_django) + actions(:deploy) + + # @!attribute allowed_hosts + # Value for `ALLOWED_HOSTS` in the Django settings. + # @return [String, Array] + attribute(:allowed_hosts, kind_of: [String, Array], default: lazy { [] }) + # @!attribute collectstatic + # Set to false to disable running manage.py collectstatic during + # deployment. + # @todo This could auto-detect based on config vars in settings? + # @return [Boolean] + attribute(:collectstatic, equal_to: [true, false], default: true) + # @!attribute database + # Option collector attribute for Django database configuration. + # @return [Hash] + # @example Setting via block + # database do + # engine 'postgresql' + # database 'blog' + # end + # @example Setting via URL + # database 'postgresql://localhost/blog' + attribute(:database, option_collector: true, parser: :parse_database_url, forced_keys: %i{name}) + # @!attribute debug + # Enable debug mode for Django. + # @note + # If you use this in production you will get everything you deserve. + # @return [Boolean] + attribute(:debug, equal_to: [true, false], default: false) + # @!attribute group + # Owner for the Django application, defaults to application group. + # @return [String] + attribute(:group, kind_of: String, default: lazy { parent && parent.group }) + # @!attribute local_settings + # Template content attribute for the contents of local_settings.py. + # @todo Redo this doc to cover the actual attributes created. + # @return [Poise::Helpers::TemplateContent] + attribute(:local_settings, template: true, default_source: 'settings.py.erb', default_options: lazy { default_local_settings_options }) + # @!attribute local_settings_path + # Path to write local settings to. If given as a relative path, + # will be expanded against {#path}. Set to false to disable writing + # local settings. Defaults to local_settings.py next to + # {#setting_module}. + # @return [String, nil false] + attribute(:local_settings_path, kind_of: [String, NilClass, FalseClass], default: lazy { default_local_settings_path }) + # @!attribute migrate + # Run database migrations. This is a bad idea for real apps. Please + # do not use it. + # @return [Boolean] + attribute(:migrate, equal_to: [true, false], default: false) + # @!attribute manage_path + # Path to manage.py. Defaults to scanning for the nearest manage.py + # to {#path}. + # @return [String] + attribute(:manage_path, kind_of: String, default: lazy { default_manage_path }) + # @!attribute owner + # Owner for the Django application, defaults to application owner. + # @return [String] + attribute(:owner, kind_of: String, default: lazy { parent && parent.owner }) + # @!attribute secret_key + # Value for `SECRET_KEY` in the Django settings. If unset, no key is + # added to the local settings. + # @return [String, false] + attribute(:secret_key, kind_of: [String, FalseClass]) + # @!attribute settings_module + # Django settings module in dotted notation. Set to false to disable + # anything related to settings. Defaults to scanning for the nearest + # settings.py to {#path}. + # @return [Boolean] + attribute(:settings_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_settings_module }) + # @!attribute syncdb + # Run database sync. This is a bad idea for real apps. Please do not + # use it. + # @return [Boolean] + attribute(:syncdb, equal_to: [true, false], default: false) + # @!attribute wsgi_module + # WSGI application module in dotted notation. Set to false to disable + # anything related to WSGI. Defaults to scanning for the nearest + # wsgi.py to {#path}. + # @return [Boolean] + attribute(:wsgi_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_wsgi_module }) + + private + + # Default value for {#local_settings_options}. Adds Django settings data + # from the resource to be rendered in the local settings template. + # + # @return [Hash] + def default_local_settings_options + {}.tap do |options| + options[:allowed_hosts] = Array(allowed_hosts) + options[:databases] = {} + options[:databases]['default'] = database.inject({}) do |memo, (key, value)| + key = key.to_s.upcase + # Deal with engine aliases here too, just in case. + value = resolve_engine(value) if key == 'ENGINE' + memo[key] = value + memo + end + options[:debug] = debug + options[:secret_key] = secret_key + end + end + + # Default value for {#local_settings_path}, local_settings.py next to + # the configured {#settings_module}. + # + # @return [String, nil] + def default_local_settings_path + # If no settings module, no default local settings. + return unless settings_module + settings_path = PoisePython::Utils.module_to_path(settings_module, path) + ::File.expand_path(::File.join('..', 'local_settings.py'), settings_path) + end + + # Default value for {#manage_path}, searches for manage.py in the + # application path. + # + # @return [String, nil] + def default_manage_path + find_file('manage.py') + end + + # Default value for {#settings_module}, searches for settings.py in the + # application path. + # + # @return [String, nil] + def default_settings_module + settings_path = find_file('settings.py') + if settings_path + PoisePython::Utils.path_to_module(settings_path, path) + else + nil + end + end + + # Default value for {#wsgi_module}, searchs for wsgi.py in the + # application path. + # + # @return [String, nil] + def default_wsgi_module + wsgi_path = find_file('wsgi.py') + if wsgi_path + PoisePython::Utils.path_to_module(wsgi_path, path) + else + nil + end + end + + # Format a URL for DATABASES. + # + # @return [Hash] + def parse_database_url(url) + parsed = URI(url) + {}.tap do |db| + # Store this for use later in #set_state, and maybe future use by + # Django in some magic world where operability happens. + db[:URL] = url + db[:ENGINE] = resolve_engine(parsed.scheme) + # Strip the leading /. + path = parsed.path ? parsed.path[1..-1] : parsed.path + # If we are using SQLite, make it an absolute path. + path = ::File.expand_path(path, self.path) if db[:ENGINE].include?('sqlite') + db[:NAME] = path if path && !path.empty? + db[:USER] = parsed.user if parsed.user && !parsed.user.empty? + db[:PASSWORD] = parsed.password if parsed.password && !parsed.password.empty? + db[:HOST] = parsed.host if parsed.host && !parsed.host.empty? + db[:PORT] = parsed.port if parsed.port && !parsed.port.nil? + end + end + + # Search for a file somewhere under the application path. Prefers files + # closer to the root, then sort alphabetically for stability. + # + # @param name [String] Filename to search for. + # @return [String, nil] + def find_file(name) + num_separators = lambda do |path| + if ::File::ALT_SEPARATOR && path.include?(::File::ALT_SEPARATOR) + # :nocov: + path.count(::File::ALT_SEPARATOR) + # :nocov: + else + path.count(::File::SEPARATOR) + end + end + Dir[::File.join(path, '**', name)].min do |a, b| + cmp = num_separators.call(a) <=> num_separators.call(b) + if cmp == 0 + cmp = a <=> b + end + cmp + end + end + + # Resolve Django database engine from shortname to dotted module. + # + # @param name [String, nil] Engine name. + # @return [String, nil] + def resolve_engine(name) + if name && !name.empty? && !name.include?('.') + ENGINE_ALIASES[name] || "django.db.backends.#{name}" + else + name + end + end + + end + + # Provider for `application_django`. + # + # @since 4.0.0 + # @see Resource + # @provides application_django + class Provider < Chef::Provider + include PoiseApplicationPython::AppMixin + provides(:application_django) + + # `deploy` action for `application_django`. Ensure all configuration + # files are created and other deploy tasks resolved. + # + # @return [void] + def action_deploy + set_state + notifying_block do + write_config + run_syncdb + run_migrate + run_collectstatic + end + end + + private + + # Set app_state variables for future services et al. + def set_state + # Set environment variables for later services. + new_resource.app_state_environment[:DJANGO_SETTINGS_MODULE] = new_resource.settings_module if new_resource.settings_module + new_resource.app_state_environment[:DATABASE_URL] = new_resource.database[:URL] if new_resource.database[:URL] + # Set the app module. + new_resource.app_state[:python_wsgi_module] = new_resource.wsgi_module if new_resource.wsgi_module + end + + # Create the database using the older syncdb command. + def run_syncdb + manage_py_execute('syncdb', '--noinput') if new_resource.syncdb + end + + # Create the database using the newer migrate command. This should work + # for either South or the built-in migrations support. + def run_migrate + manage_py_execute('migrate', '--noinput') if new_resource.migrate + end + + # Run the asset pipeline. + def run_collectstatic + manage_py_execute('collectstatic', '--noinput') if new_resource.collectstatic + end + + # Create the local config settings. + def write_config + # Allow disabling the local settings. + return unless new_resource.local_settings_path + file new_resource.local_settings_path do + content new_resource.local_settings_content + mode '640' + owner new_resource.owner + group new_resource.group + end + end + + # Run a manage.py command using `python_execute`. + def manage_py_execute(*cmd) + raise PoiseApplicationPython::Error.new("Unable to find a find a manage.py for #{new_resource}, please set manage_path") unless new_resource.manage_path + python_execute "manage.py #{cmd.join(' ')}" do + python_from_parent new_resource + command [::File.expand_path(new_resource.manage_path, new_resource.path)] + cmd + cwd new_resource.path + environment new_resource.app_state_environment + group new_resource.group + user new_resource.owner + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/gunicorn.rb b/lib/poise_application_python/resources/gunicorn.rb new file mode 100644 index 0000000..e9daed3 --- /dev/null +++ b/lib/poise_application_python/resources/gunicorn.rb @@ -0,0 +1,127 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'shellwords' + +require 'chef/provider' +require 'chef/resource' +require 'poise' + +require 'poise_application_python/service_mixin' + + +module PoiseApplicationPython + module Resources + # (see Gunicorn::Resource) + # @since 4.0.0 + module Gunicorn + class Resource < Chef::Resource + include PoiseApplicationPython::ServiceMixin + provides(:application_gunicorn) + + attribute(:app_module, kind_of: [String, NilClass], default: lazy { default_app_module }) + attribute(:bind, kind_of: [String, Array], default: '0.0.0.0:80') + attribute(:config, kind_of: [String, NilClass]) + attribute(:preload_app, equal_to: [true, false], default: false) + attribute(:version, kind_of: [String, TrueClass, FalseClass], default: true) + + # Helper to set {#bind} with just a port number. + # + # @param val [String, Integer] Port number to use. + # @return [void] + def port(val) + bind("0.0.0.0:#{val}") + end + + private + + # Compute the default application module to pass to gunicorn. This + # checks the app state and then looks for commonly used filenames. + # Returns nil if no default can be found. + # + # @return [String] + def default_app_module + # If set in app_state, use that. + return app_state[:python_wsgi_module] if app_state[:python_wsgi_module] + files = Dir.exist?(path) ? Dir.entries(path) : [] + # Try to find a known filename. + candidate_file = %w{wsgi.py main.py app.py application.py}.find {|file| files.include?(file) } + # Try the first Python file. Do I really want this? + candidate_file ||= files.find {|file| file.end_with?('.py') } + if candidate_file + ::File.basename(candidate_file, '.py') + else + nil + end + end + + end + + class Provider < Chef::Provider + include PoiseApplicationPython::ServiceMixin + provides(:application_gunicorn) + + def action_enable + notifying_block do + install_gunicorn + end + super + end + + private + + def install_gunicorn + return unless new_resource.version + python_package 'gunicorn' do + python_from_parent new_resource + version new_resource.version if new_resource.version.is_a?(String) + end + end + + def gunicorn_command_options + # Based on http://docs.gunicorn.org/en/latest/settings.html + [].tap do |cmd| + # What options are common enough to deal with here? + # %w{config backlog workers worker_class threads worker_connections timeout graceful_timeout keepalive}.each do |opt| + %w{config}.each do |opt| + val = new_resource.send(opt) + if val && !(val.respond_to?(:empty?) && val.empty?) + cmd_opt = opt.gsub(/_/, '-') + cmd << "--#{cmd_opt} #{Shellwords.escape(val)}" + end + end + # Can be given multiple times. + Array(new_resource.bind).each do |bind| + cmd << "--bind #{bind}" if bind + end + # --preload doesn't take an argument and the name doesn't match. + if new_resource.preload_app + cmd << '--preload' + end + end + end + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + raise PoiseApplicationPython::Error.new("Unable to determine app module for #{new_resource}") unless new_resource.app_module + resource.command("#{new_resource.python} -m gunicorn.app.wsgiapp #{gunicorn_command_options.join(' ')} #{new_resource.app_module}") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/pip_requirements.rb b/lib/poise_application_python/resources/pip_requirements.rb new file mode 100644 index 0000000..8c236d8 --- /dev/null +++ b/lib/poise_application_python/resources/pip_requirements.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_python/resources/pip_requirements' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PipRequirements::Resource) + # @since 4.0.0 + module PipRequirements + # An `application_pip_requirements` resource to manage Python virtual + # environments inside an Application cookbook deployment. + # + # @provides application_pip_requirements + # @provides application_virtualenv + # @action install + # @action upgrade + # @example + # application '/app' do + # pip_requirements + # end + class Resource < PoisePython::Resources::PipRequirements::Resource + include PoiseApplicationPython::AppMixin + provides(:application_pip_requirements) + subclass_providers! + + # @todo This should handle relative paths against parent.path. + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.owner }) + end + end + end +end diff --git a/lib/poise_application_python/resources/python.rb b/lib/poise_application_python/resources/python.rb new file mode 100644 index 0000000..021bec0 --- /dev/null +++ b/lib/poise_application_python/resources/python.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_python/resources/python_runtime' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see Python::Resource) + # @since 4.0.0 + module Python + # An `application_python` resource to manage Python runtimes + # inside an Application cookbook deployment. + # + # @provides application_python + # @provides application_python_runtime + # @action install + # @action uninstall + # @example + # application '/app' do + # python '2' + # end + class Resource < PoisePython::Resources::PythonRuntime::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python) + provides(:application_python_runtime) + container_default(false) + subclass_providers! + + # Set this resource as the app_state's parent python. + # + # @api private + def after_created + super.tap do |val| + app_state_python(self) + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/python_execute.rb b/lib/poise_application_python/resources/python_execute.rb new file mode 100644 index 0000000..6b79fdf --- /dev/null +++ b/lib/poise_application_python/resources/python_execute.rb @@ -0,0 +1,89 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_python/resources/python_execute' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PythonExecute::Resource) + # @since 4.0.0 + module PythonExecute + # An `application_python_execute` resource to run Python commands inside an + # Application cookbook deployment. + # + # @provides application_python_execute + # @action run + # @example + # application '/srv/myapp' do + # python_execute 'setup.py install' + # end + class Resource < PoisePython::Resources::PythonExecute::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python_execute) + + def initialize(*args) + super + # Clear some instance variables so my defaults work. + remove_instance_variable(:@cwd) if defined?(@cwd) + remove_instance_variable(:@group) if defined?(@group) + remove_instance_variable(:@user) if defined?(@user) + end + + # #!attribute cwd + # Override the default directory to be the app path if unspecified. + # @return [String] + attribute(:cwd, kind_of: [String, NilClass, FalseClass], default: lazy { parent && parent.path }) + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass, FalseClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass, FalseClass], default: lazy { parent && parent.owner }) + end + + # The default provider for `application_python_execute`. + # + # @see Resource + # @provides application_python_execute + class Provider < PoisePython::Resources::PythonExecute::Provider + provides(:application_python_execute) + + private + + # Override environment to add the application envivonrment instead. + # + # @return [Hash] + def environment + super.tap do |environment| + # Don't use the app_state_environment_python because we already have + # those values in place. + environment.update(new_resource.app_state_environment) + # Re-apply the resource environment for correct ordering. + environment.update(new_resource.environment) if new_resource.environment + end + end + end + + end + end +end diff --git a/lib/poise_application_python/resources/python_package.rb b/lib/poise_application_python/resources/python_package.rb new file mode 100644 index 0000000..54ec9ee --- /dev/null +++ b/lib/poise_application_python/resources/python_package.rb @@ -0,0 +1,62 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_python/resources/python_package' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PythonPackage::Resource) + # @since 4.0.0 + module PythonPackage + # An `application_python_package` resource to install Python + # packages inside an Application cookbook deployment. + # + # @provides application_python_package + # @action install + # @action upgrade + # @action remove + # @example + # application '/srv/myapp' do + # python_package 'requests' + # end + class Resource < PoisePython::Resources::PythonPackage::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python_package) + subclass_providers! + + def initialize(*args) + super + # For older Chef. + @resource_name = :application_python_package + end + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.owner }) + end + + end + end +end diff --git a/lib/poise_application_python/resources/virtualenv.rb b/lib/poise_application_python/resources/virtualenv.rb new file mode 100644 index 0000000..3bec6fa --- /dev/null +++ b/lib/poise_application_python/resources/virtualenv.rb @@ -0,0 +1,75 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_python/resources/python_virtualenv' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see Virtualenv::Resource) + # @since 4.0.0 + module Virtualenv + # An `application_virtualenv` resource to manage Python virtual + # environments inside an Application cookbook deployment. + # + # @provides application_virtualenv + # @provides application_python_virtualenv + # @action create + # @action delete + # @example + # application '/app' do + # virtualenv + # end + class Resource < PoisePython::Resources::PythonVirtualenv::Resource + include PoiseApplicationPython::AppMixin + provides(:application_virtualenv) + provides(:application_python_virtualenv) + container_default(false) + subclass_providers! + + # @!attribute path + # Override the normal path property to use name/.virtualenv for better + # compatibility with the application resource DSL. + # @return [String] + attribute(:path, kind_of: String, default: lazy { default_path }) + + # Set this resource as the app_state's parent python. + # + # @api private + def after_created + super.tap do |val| + # Force evaluation so we get any current parent if set. + parent_python + app_state_python(self) + end + end + + private + + # Default value for the {#path} property. + # + # @return [String] + def default_path + # @todo This should handle relative paths as a name. + ::File.join(name, '.virtualenv') + end + + end + end + end +end diff --git a/lib/poise_application_python/service_mixin.rb b/lib/poise_application_python/service_mixin.rb new file mode 100644 index 0000000..2c52420 --- /dev/null +++ b/lib/poise_application_python/service_mixin.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise/utils' +require 'poise_application/service_mixin' +require 'poise_languages/utils' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + # A helper mixin for Python service resources and providers. + # + # @since 4.0.0 + module ServiceMixin + include Poise::Utils::ResourceProviderMixin + + # A helper mixin for Python service resources. + module Resource + include PoiseApplication::ServiceMixin::Resource + include PoiseApplicationPython::AppMixin::Resource + end + + # A helper mixin for Python service providers. + module Provider + include PoiseApplication::ServiceMixin::Provider + include PoiseApplicationPython::AppMixin::Provider + + # Set up the service for running Python stuff. + def service_options(resource) + super + # Closure scoping for #python_command below. + self_ = self + # Create a new singleton method that fills in Python for you. + resource.define_singleton_method(:python_command) do |val| + resource.command("#{self_.new_resource.python} #{PoiseLanguages::Utils.absolute_command(val, path: self_.new_resource.app_state_environment_python['PATH'])}") + end + # Include env vars as needed. + resource.environment.update(new_resource.parent_python.python_environment) if new_resource.parent_python + end + + end + end +end diff --git a/lib/poise_application_python/version.rb b/lib/poise_application_python/version.rb new file mode 100644 index 0000000..50af440 --- /dev/null +++ b/lib/poise_application_python/version.rb @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +module PoiseApplicationPython + VERSION = '4.0.1.pre' +end diff --git a/metadata.rb b/metadata.rb deleted file mode 100644 index 82df969..0000000 --- a/metadata.rb +++ /dev/null @@ -1,13 +0,0 @@ -name "application_python" -maintainer "Opscode, Inc." -maintainer_email "cookbooks@opscode.com" -license "Apache 2.0" -description "Deploys and configures Python-based applications" -long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version "2.0.3" - -%w{ python gunicorn supervisor }.each do |cb| - depends cb -end - -depends "application", "~> 3.0" diff --git a/poise-application-python.gemspec b/poise-application-python.gemspec new file mode 100644 index 0000000..8394013 --- /dev/null +++ b/poise-application-python.gemspec @@ -0,0 +1,48 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'poise_application_python/version' + +Gem::Specification.new do |spec| + spec.name = 'poise-application-python' + spec.version = PoiseApplicationPython::VERSION + spec.authors = ['Noah Kantrowitz'] + spec.email = %w{noah@coderanger.net} + spec.description = "A Chef cookbook for deploying Python application code." + spec.summary = spec.description + spec.homepage = 'https://github.com/poise/application_python' + spec.license = 'Apache-2.0' + spec.metadata['halite_name'] = 'application_python' + spec.metadata['platforms'] = 'any' + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = %w{lib} + + spec.add_dependency 'chef', '>= 12.1', '< 14' + spec.add_dependency 'halite', '~> 1.0' + spec.add_dependency 'poise', '~> 2.0' + spec.add_dependency 'poise-application', '~> 5.0' + spec.add_dependency 'poise-python', '~> 1.0' + spec.add_dependency 'poise-service', '~> 1.0' + + spec.add_development_dependency 'poise-boiler', '~> 1.6' + spec.add_development_dependency 'poise-application-git', '~> 1.2' + spec.add_development_dependency 'poise-build-essential', '~> 1.0' +end diff --git a/providers/celery.rb b/providers/celery.rb deleted file mode 100644 index 1280c32..0000000 --- a/providers/celery.rb +++ /dev/null @@ -1,122 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: django -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe "supervisor" - - raise "You must specify an application module to load" unless new_resource.config - - if !new_resource.restart_command - new_resource.restart_command do - run_context.resource_collection.find(:supervisor_service => "#{new_resource.application.name}-celeryd").run_action(:restart) if new_resource.celeryd - run_context.resource_collection.find(:supervisor_service => "#{new_resource.application.name}-celerybeat").run_action(:restart) if new_resource.celerybeat - run_context.resource_collection.find(:supervisor_service => "#{new_resource.application.name}-celerycam").run_action(:restart) if new_resource.celerycam - end - end - - new_resource.symlink_before_migrate.update({ - new_resource.config_base => new_resource.config, - }) - - new_resource.broker[:transport] ||= "amqplib" - new_resource.broker[:host_role] ||= "#{new_resource.application.name}_task_broker" - new_resource.broker[:host] ||= begin - host = new_resource.find_matching_role(new_resource.broker[:host_role]) - raise "No task broker host found" unless host - host.attribute?('cloud') ? host['cloud']['local_ipv4'] : host['ipaddress'] - end -end - -action :before_deploy do - - new_resource = @new_resource - - template ::File.join(new_resource.application.path, "shared", new_resource.config_base) do - source new_resource.template || "celeryconfig.py.erb" - cookbook new_resource.template ? new_resource.cookbook_name.to_s : "application_python" - owner new_resource.owner - group new_resource.group - mode "644" - variables :broker => new_resource.broker, :results => new_resource.results - end - - if new_resource.celerycam - # turn on events automatically, if we are going to run celerycam - new_resource.enable_events(true) - end - - cmds = {} - if new_resource.celeryd - case new_resource.queues - when Array - cmds[:celeryd] = "celeryd -Q #{new_resource.queues.join(',')} #{new_resource.enable_events ? "-E" : ""}" - when NilClass - cmds[:celeryd] = "celeryd #{new_resource.enable_events ? "-E" : ""}" - end - end - cmds[:celerybeat] = "celerybeat" if new_resource.celerybeat - if new_resource.celerycam - if new_resource.django - cmd = "celerycam" - else - raise "No camera class specified" unless new_resource.camera_class - cmd = "celeryev --camera=\"#{new_resource.camera_class}\"" - end - cmds[:celerycam] = cmd - end - - cmds.each do |type, cmd| - supervisor_service "#{new_resource.application.name}-#{type}" do - action :enable - if new_resource.django - django_resource = new_resource.application.sub_resources.select{|res| res.type == :django}.first - raise "No Django deployment resource found" unless django_resource - command "#{::File.join(django_resource.virtualenv, "bin", "python")} manage.py #{cmd}" - environment new_resource.environment - else - command cmd - if new_resource.environment - environment new_resource.environment.merge({'CELERY_CONFIG_MODULE' => new_resource.config}) - else - environment 'CELERY_CONFIG_MODULE' => new_resource.config - end - end - directory ::File.join(new_resource.path, "current") - autostart false - user new_resource.owner - end - end - -end - -action :before_migrate do -end - -action :before_symlink do -end - -action :before_restart do -end - -action :after_restart do -end diff --git a/providers/django.rb b/providers/django.rb deleted file mode 100644 index 798564a..0000000 --- a/providers/django.rb +++ /dev/null @@ -1,146 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: django -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -require 'tmpdir' - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe 'python' - - new_resource.migration_command "#{::File.join(new_resource.virtualenv, "bin", "python")} manage.py syncdb --noinput" if !new_resource.migration_command - - new_resource.symlink_before_migrate.update({ - new_resource.local_settings_base => new_resource.local_settings_file, - }) -end - -action :before_deploy do - - install_packages - - created_settings_file - -end - -action :before_migrate do - - if new_resource.requirements.nil? - # look for requirements.txt files in common locations - [ - ::File.join(new_resource.release_path, "requirements", "#{node.chef_environment}.txt"), - ::File.join(new_resource.release_path, "requirements.txt") - ].each do |path| - if ::File.exists?(path) - new_resource.requirements path - break - end - end - end - if new_resource.requirements - Chef::Log.info("Installing using requirements file: #{new_resource.requirements}") - pip_cmd = ::File.join(new_resource.virtualenv, 'bin', 'pip') - execute "#{pip_cmd} install --source=#{Dir.tmpdir} -r #{new_resource.requirements}" do - cwd new_resource.release_path - # seems that if we don't set the HOME env var pip tries to log to /root/.pip, which fails due to permissions - # setting HOME also enables us to control pip behavior on per-project basis by dropping off a pip.conf file there - # GIT_SSH allow us to reuse the deployment key used to clone the main - # repository to clone any private requirements - if new_resource.deploy_key - environment 'HOME' => ::File.join(new_resource.path,'shared'), 'GIT_SSH' => "#{new_resource.path}/deploy-ssh-wrapper" - else - environment 'HOME' => ::File.join(new_resource.path,'shared') - end - user new_resource.owner - group new_resource.group - end - else - Chef::Log.debug("No requirements file found") - end - -end - -action :before_symlink do - - if new_resource.collectstatic - cmd = new_resource.collectstatic.is_a?(String) ? new_resource.collectstatic : "collectstatic --noinput" - execute "#{::File.join(new_resource.virtualenv, "bin", "python")} manage.py #{cmd}" do - user new_resource.owner - group new_resource.group - cwd new_resource.release_path - end - end - - ruby_block "remove_run_migrations" do - block do - if node.role?("#{new_resource.application.name}_run_migrations") - Chef::Log.info("Migrations were run, removing role[#{new_resource.name}_run_migrations]") - node.run_list.remove("role[#{new_resource.name}_run_migrations]") - end - end - end - -end - -action :before_restart do -end - -action :after_restart do -end - -protected - -def install_packages - python_virtualenv new_resource.virtualenv do - path new_resource.virtualenv - owner new_resource.owner - group new_resource.group - action :create - end - - new_resource.packages.each do |name, ver| - python_pip name do - version ver if ver && ver.length > 0 - virtualenv new_resource.virtualenv - user new_resource.owner - group new_resource.group - action :install - end - end -end - -def created_settings_file - host = new_resource.find_database_server(new_resource.database_master_role) - - template "#{new_resource.path}/shared/#{new_resource.local_settings_base}" do - source new_resource.settings_template || "settings.py.erb" - cookbook new_resource.settings_template ? new_resource.cookbook_name.to_s : "application_python" - owner new_resource.owner - group new_resource.group - mode "644" - variables new_resource.settings.clone - variables.update :debug => new_resource.debug, :database => { - :host => host, - :settings => new_resource.database, - :legacy => new_resource.legacy_database_settings - } - end -end diff --git a/providers/gunicorn.rb b/providers/gunicorn.rb deleted file mode 100644 index a8bbd74..0000000 --- a/providers/gunicorn.rb +++ /dev/null @@ -1,146 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: gunicorn -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -require 'tmpdir' - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe "supervisor" - - install_packages - - django_resource = new_resource.application.sub_resources.select{|res| res.type == :django}.first - gunicorn_install "gunicorn-#{new_resource.application.name}" do - virtualenv django_resource ? django_resource.virtualenv : new_resource.virtualenv - end - - if !new_resource.restart_command - new_resource.restart_command do - run_context.resource_collection.find(:supervisor_service => new_resource.application.name).run_action(:restart) - end - end - - raise "You must specify an application module to load" unless new_resource.app_module - -end - -action :before_deploy do - - new_resource = @new_resource - - gunicorn_config "#{new_resource.application.path}/shared/gunicorn_config.py" do - action :create - template new_resource.settings_template || 'gunicorn.py.erb' - cookbook new_resource.settings_template ? new_resource.cookbook_name.to_s : 'gunicorn' - listen "#{new_resource.host}:#{new_resource.port}" - backlog new_resource.backlog - worker_processes new_resource.workers - worker_class new_resource.worker_class.to_s - #worker_connections - worker_max_requests new_resource.max_requests - worker_timeout new_resource.timeout - worker_keepalive new_resource.keepalive - #debug - #trace - preload_app new_resource.preload_app - #daemon - pid new_resource.pidfile - #umask - #logfile - #loglevel - #proc_name - end - - supervisor_service new_resource.application.name do - action :enable - if new_resource.environment - environment new_resource.environment - end - if new_resource.app_module == :django - django_resource = new_resource.application.sub_resources.select{|res| res.type == :django}.first - raise "No Django deployment resource found" unless django_resource - base_command = "#{::File.join(django_resource.virtualenv, "bin", "python")} manage.py run_gunicorn" - else - gunicorn_command = new_resource.virtualenv.nil? ? "gunicorn" : "#{::File.join(new_resource.virtualenv, "bin", "gunicorn")}" - base_command = "#{gunicorn_command} #{new_resource.app_module}" - end - command "#{base_command} -c #{new_resource.application.path}/shared/gunicorn_config.py" - directory new_resource.directory.nil? ? ::File.join(new_resource.path, "current") : new_resource.directory - autostart new_resource.autostart - user new_resource.owner - end - -end - -action :before_migrate do - install_requirements -end - -action :before_symlink do -end - -action :before_restart do -end - -action :after_restart do -end - -protected - -def install_packages - new_resource.packages.each do |name, ver| - python_pip name do - version ver if ver && ver.length > 0 - virtualenv new_resource.virtualenv - action :install - end - end -end - -def install_requirements - if new_resource.requirements.nil? - # look for requirements.txt files in common locations - [ - ::File.join(new_resource.release_path, "requirements", "#{node.chef_environment}.txt"), - ::File.join(new_resource.release_path, "requirements.txt") - ].each do |path| - if ::File.exists?(path) - new_resource.requirements path - break - end - end - end - if new_resource.requirements - Chef::Log.info("Installing using requirements file: #{new_resource.requirements}") - # TODO normalise with python/providers/pip.rb 's pip_cmd - if new_resource.virtualenv.nil? - pip_cmd = 'pip' - else - pip_cmd = ::File.join(new_resource.virtualenv, 'bin', 'pip') - end - execute "#{pip_cmd} install --src=#{Dir.tmpdir} -r #{new_resource.requirements}" do - cwd new_resource.release_path - end - else - Chef::Log.debug("No requirements file found") - end -end diff --git a/resources/celery.rb b/resources/celery.rb deleted file mode 100644 index 205aac0..0000000 --- a/resources/celery.rb +++ /dev/null @@ -1,48 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: celery -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -include ApplicationCookbook::ResourceBase - -attribute :config, :kind_of => [String, NilClass], :default => nil -attribute :template, :kind_of => [String, NilClass], :default => nil -attribute :django, :kind_of => [TrueClass, FalseClass], :default => false -attribute :celeryd, :kind_of => [TrueClass, FalseClass], :default => true -attribute :celerybeat, :kind_of => [TrueClass, FalseClass], :default => false -attribute :celerycam, :kind_of => [TrueClass, FalseClass], :default => false -attribute :camera_class, :kind_of => [String, NilClass], :default => nil -attribute :enable_events, :kind_of => [TrueClass, FalseClass], :default => false -attribute :environment, :kind_of => [Hash], :default => {} -attribute :queues, :kind_of => [Array,NilClass], :default => nil - -def config_base - config.split(/[\\\/]/).last -end - -def broker(*args, &block) - @broker ||= Mash.new - @broker.update(options_block(*args, &block)) - @broker -end - -def results(*args, &block) - @results ||= Mash.new - @results.update(options_block(*args, &block)) - @results -end diff --git a/resources/django.rb b/resources/django.rb deleted file mode 100644 index 5c5e601..0000000 --- a/resources/django.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: django -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -include ApplicationCookbook::ResourceBase - -attribute :database_master_role, :kind_of => [String, NilClass], :default => nil -attribute :packages, :kind_of => [Array, Hash], :default => [] -attribute :requirements, :kind_of => [NilClass, String, FalseClass], :default => nil -attribute :legacy_database_settings, :kind_of => [TrueClass, FalseClass], :default => false -attribute :settings, :kind_of => Hash, :default => {} -# Actually defaults to "settings.py.erb", but nil means it wasn't set by the user -attribute :settings_template, :kind_of => [String, NilClass], :default => nil -attribute :local_settings_file, :kind_of => String, :default => 'local_settings.py' -attribute :debug, :kind_of => [TrueClass, FalseClass], :default => false -attribute :collectstatic, :kind_of => [TrueClass, FalseClass, String], :default => false - -def local_settings_base - local_settings_file.split(/[\\\/]/).last -end - -def virtualenv - "#{path}/shared/env" -end - -def database(*args, &block) - @database ||= Mash.new - @database.update(options_block(*args, &block)) - @database -end diff --git a/resources/gunicorn.rb b/resources/gunicorn.rb deleted file mode 100644 index 6c5eb29..0000000 --- a/resources/gunicorn.rb +++ /dev/null @@ -1,49 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: gunicorn -# -# Copyright:: 2011, Opscode, Inc -# -# 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 -# -# 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. -# - -include ApplicationCookbook::ResourceBase - -attribute :app_module, :kind_of => [String, Symbol, NilClass], :default => nil -# Actually defaults to "settings.py.erb", but nil means it wasn't set by the user -attribute :settings_template, :kind_of => [String, NilClass], :default => nil -attribute :host, :kind_of => String, :default => '0.0.0.0' -attribute :port, :kind_of => Integer, :default => 8080 -attribute :backlog, :kind_of => Integer, :default => 2048 -attribute :workers, :kind_of => Integer, :default => (node['cpu'] && node['cpu']['total']) && [node['cpu']['total'].to_i * 4, 8].min || 8 -attribute :worker_class, :kind_of => [String, Symbol], :default => :sync -attribute :worker_connections, :kind_of => Integer, :default => 1000 -attribute :max_requests, :kind_of => Integer, :default => 0 -attribute :timeout, :kind_of => Integer, :default => 30 -attribute :keepalive, :kind_of => Integer, :default => 2 -attribute :debug, :kind_of => [TrueClass, FalseClass], :default => false -attribute :trace, :kind_of => [TrueClass, FalseClass], :default => false -attribute :preload_app, :kind_of => [TrueClass, FalseClass], :default => false -attribute :daemon, :kind_of => [TrueClass, FalseClass], :default => false -attribute :pidfile, :kind_of => [String, NilClass], :default => nil -attribute :umask, :kind_of => [String, Integer], :default => 0 -attribute :logfile, :kind_of => String, :default => '-' -attribute :loglevel, :kind_of => [String, Symbol], :default => :info -attribute :proc_name, :kind_of => [String, NilClass], :default => nil -attribute :virtualenv, :kind_of => String, :default => nil -attribute :packages, :kind_of => [Array, Hash], :default => [] -attribute :requirements, :kind_of => [NilClass, String, FalseClass], :default => nil -attribute :environment, :kind_of => [Hash], :default => {} -attribute :autostart, :kind_of => [TrueClass, FalseClass], :default => false -attribute :directory, :kind_of => [NilClass, String], :default => nil diff --git a/templates/default/celeryconfig.py.erb b/templates/default/celeryconfig.py.erb deleted file mode 100644 index 1aad650..0000000 --- a/templates/default/celeryconfig.py.erb +++ /dev/null @@ -1,15 +0,0 @@ -BROKER_TRANSPORT = "<%= @broker[:transport] %>" -BROKER_HOST = "<%= @broker[:host] %>" -<% %w{port user password vhost}.each do |key| %> -<% if @broker[key] %> -BROKER_<%= key.upcase %> = "<%= @broker[key] %>" -<% end %> -<% end %> -<% %w{pool_limit connection_timeout connection_retry connection_max_retries}.each do |key| %> -<% if @broker[key] %> -BROKER_<%= key.upcase %> = <%= @broker[key] %> -<% end %> -<% end %> -<% if @broker[:use_ssl] %> -BROKER_USE_SSL = True -<% end %> diff --git a/templates/default/settings.py.erb b/templates/default/settings.py.erb deleted file mode 100644 index 794eb25..0000000 --- a/templates/default/settings.py.erb +++ /dev/null @@ -1,20 +0,0 @@ -DEBUG = <%= @debug ? "True" : "False" %> - -DATABASES = { - 'default': { - 'NAME': '<%= @database[:settings][:database] %>', - 'ENGINE': 'django.db.backends.<%= @database[:settings][:adapter] %>', - 'USER': '<%= @database[:settings][:username] %>', - 'PASSWORD': '<%= @database[:settings][:password] %>', - 'HOST': '<%= @database[:host] %>', - 'PORT': '<%= @database[:settings][:port] %>', - }, -} -<% if @database[:legacy] -%> -DATABASE_ENGINE = DATABASES['default']['ENGINE'] -DATABASE_NAME = DATABASES['default']['NAME'] -DATABASE_USER = DATABASES['default']['USER'] -DATABASE_PASSWORD = DATABASES['default']['PASSWORD'] -DATABASE_HOST = DATABASES['default']['HOST'] -DATABASE_PORT = DATABASES['default']['PORT'] -<% end -%> diff --git a/test/cookbook/attributes/default.rb b/test/cookbook/attributes/default.rb new file mode 100644 index 0000000..56cb0d7 --- /dev/null +++ b/test/cookbook/attributes/default.rb @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +override['poise-service']['provider'] = 'dummy' diff --git a/test/cookbook/metadata.rb b/test/cookbook/metadata.rb new file mode 100644 index 0000000..149dbeb --- /dev/null +++ b/test/cookbook/metadata.rb @@ -0,0 +1,20 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +name 'application_python_test' +depends 'application_git' +depends 'application_python' +depends 'poise-build-essential' diff --git a/test/cookbook/recipes/default.rb b/test/cookbook/recipes/default.rb new file mode 100644 index 0000000..e1db35e --- /dev/null +++ b/test/cookbook/recipes/default.rb @@ -0,0 +1,85 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +include_recipe 'poise-python' + +# For netstat in serverspec. +package 'net-tools' + +application '/opt/wsgi1' do + file '/opt/wsgi1/main.py' do + content <<-'EOH' +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['Hello world!\n'] +EOH + end + gunicorn do + port 8000 + end + gunicorn 'wsgi1b' do + path parent.path + service_name 'wsgi1b' + app_module 'main' + port 8001 + end +end + +application '/opt/wsgi2' do + file "#{path}/main.py" do + content <<-'EOH' +import sys +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['\n'.join(sys.path)] +EOH + end + gunicorn do + port 8002 + end +end + +application '/opt/wsgi3' do + file "#{path}/requirements.txt" do + content <<-EOH +requests +six +EOH + end + virtualenv + pip_requirements + file "#{path}/main.py" do + content <<-'EOH' +import sys +import requests +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['\n'.join(sys.path)] +EOH + end + gunicorn do + port 8003 + end +end + +include_recipe '::django' +include_recipe '::flask' diff --git a/test/cookbook/recipes/django.rb b/test/cookbook/recipes/django.rb new file mode 100644 index 0000000..c115174 --- /dev/null +++ b/test/cookbook/recipes/django.rb @@ -0,0 +1,36 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +include_recipe 'poise-build-essential' +include_recipe 'poise-python' + +application '/opt/test_django' do + git 'https://github.com/poise/test_django.git' + python 'pypy3-5.7' + virtualenv + pip_requirements + django do + database 'sqlite:///test_django.db' + migrate true + end + gunicorn do + port 9000 + end + # PyPy is a bit slower to shut down. + poise_service_options '/opt/test_django' do + restart_delay 30 + end +end diff --git a/test/cookbook/recipes/flask.rb b/test/cookbook/recipes/flask.rb new file mode 100644 index 0000000..59d98f2 --- /dev/null +++ b/test/cookbook/recipes/flask.rb @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +include_recipe 'poise-python' + +application '/opt/test_flask' do + git 'https://github.com/poise/test_flask.git' + pip_requirements + gunicorn do + port 9001 + end +end diff --git a/test/docker/docker.ca b/test/docker/docker.ca new file mode 100644 index 0000000..f381108 --- /dev/null +++ b/test/docker/docker.ca @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIJAJTJgn9tSdKmMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV +BAMTAkNBMB4XDTE1MDExMjIwMjk0M1oXDTI1MDEwOTIwMjk0M1owDTELMAkGA1UE +AxMCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDPFn79sz1kQLk +rAS5z/zfCMXuE+V9IEmGXJeSprsXrv+AdjFpVDr52lpvZ36i2gixk8grcWtFqQMC +jB1c2HUe69ebC89rHSPmGCx5eRcWQPQG29fdH/nC+I34EbYadJB7PdzkvTj0KuN8 +YfQj6lhwqltYiELZhuGoXcuhwZ5SC4VcJ2cdvx7oQPECLlMft8dhWyk15WGhp0jL +2H5noGajz9IFzHieoKJyh+oYA3BYCugpNBLTweNw+NuRxMwHixftvkXvlqKeZ402 +4iwmIO8MG9oUxXs6D85gv6tJOau+dD1EDYH9VzYwSvLto3QBzJX0NiHKlmeq4BG2 +1V3n+N1kZmMDgEtX2TDFsGHlUo77Gw0ob/w7qJU+7GAXRwWw7TPhMBLSkOlGM726 +Lq9p5+7mK1YThk0PmlsSAU6/fT79PSdrYpTKr3WkBTnwd76df+Koh8fpN4BHf9L4 +9bWSYc9Nb9/wp0md9xhzjjVHargpVxZmNH7bcIa8YA9tYaW+oifo2hfb7o0qhGQ1 +8pES3LPUi/qtZkBYUQdh8/mkqTvRjeS446iUmYWcrHyiIzQk/cMbrAVYOi3Xnq0J +ui/r48iv7uLhpcDEQl2mENr0syygrPthVKa4gYHAZ0tK3pfe0yUGMiwS2D23xMR4 +WYLWLwYSK0j1JYpEbsBNS3wZX91FIwIDAQABo24wbDAdBgNVHQ4EFgQU1j2CHhNt +sWAvDmu49yRFfHRBp9kwPQYDVR0jBDYwNIAU1j2CHhNtsWAvDmu49yRFfHRBp9mh +EaQPMA0xCzAJBgNVBAMTAkNBggkAlMmCf21J0qYwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAgEAD7apefon65k3Xey7vsTb/A18m7JwBNLB48ILNcSKVgO1 +iuSMCGNQ+4bNU4o9cTpRoijB3w4RY7IIaDlRcUg8FIO6kgEhjhiAjSSqJAaajOFc +urxOmi9E7xYmTDqLxEGF5/5vaG4olAi3tRgZNd2+Ue0ANZ1KMh3ZkE0nA5v1zb/g +Ax/Zs6tATdoG6umMQg8TjiKucwi9J9he+xJ5y0E77/RrdNL9aKcU47wTAwUkokpb +u1JFo1da3yZLDwQuBN5DCc4pgPgxXlfa6DnzQM1veKIhP5sa9T4sCC8S4IjGFenw +yl4xm+9AOZQeLFpczqgVJhun5P41syepnZ433hWoLXKLHd1n0ILgw9JyVF686LIt +bSar3+krmFuzdRCfet0kJR762p8jmxJOwL+KQGELGlkleJK48a+ruWIeeulZhpJ5 +tF4QJxytq4aXpjeFma0Yi/0rQuNi3H1QIW5YPnFL0XlJ8Rvr8gSVc1zhkM9rsnxX +l9Pun0flP/mf/ulOa020hQUPqEYjSfdJOkLy2gZDvHRL2LRXNjGHoteGNJCq34Q1 +wQerxofHn+Hpp61+Ebj+RLK0KJE+QeP3T8rL30aSSzQZQZJVI0ict5C71kiTbQnw +Z0vE6LquvFfMSqfPLt6uuCRVywBjLx19B7TuMf/DgAD+lR+1FFGKy1hO2Q1jfCY= +-----END CERTIFICATE----- diff --git a/test/docker/docker.pem b/test/docker/docker.pem new file mode 100644 index 0000000..d449b2c --- /dev/null +++ b/test/docker/docker.pem @@ -0,0 +1,83 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIBGzANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDEwJDQTAe +Fw0xNTA3MjIwMDUyNTBaFw0yNTA3MTkwMDUyNTBaMGwxLTArBgNVBAMUJERvY2tl +ciBjbGllbnQgZm9yIGFwcGxpY2F0aW9uX3B5dGhvbjEXMBUGA1UECxMOa2l0Y2hl +bi1kb2NrZXIxIjAgBgNVBAoTGUNvZGVyYW5nZXIgQ29uc3VsdGluZyBMTEMwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsshKxwkNqBbhxyUKn+1z/ZWkW +Zee7IW3uYkxBnsbyXrLKMdW3ClSSvyR6YV4mL+TmfeWYR8bZBQuzABpoNa4wLaFa +JsKUAlnAVNIOP2fd1JfBk2AmqqOx9ZL3JrQv+zYZGx5HeBC1i1vZllWio0qvToMS +OEHkacOB+YvvjCEWAm3dI8vTtGN9S1J8Ql7OtpgqgZfRVIKsPK1fqnJSis9BYzMz +B5kcLI77rs5SYuLIpA7A/t6ZhciK+tOhjlHS2ctLQJ7/5R1GgppWzvxcgmfLV3Vm +a+vFYN0xwb0MQAzXgV149yk+Ap6D4aB1riUw+YJ+zhUXIzo7xteqeMw8pkgdJ9PS +IC3liPDPKGtkyzzdoS3Wmg1MZ1pdnpvFzDct+LYO5czNIbsQGO2OasoAtVjM8ZaF +ZVh0jt4LMYJZvyCkS1FF/C8DM02R26j2hHYwUhQPn5FwzPBmTzoQwihmQIcLCPil +R9YC3SQzbZ759NXSX2HQC83msP/cPVxyilXYNgY9g8sbSBR79NgZs1oYL6xvjbw3 +sTSYFoqpXlDi4b2Atna4gP1jGmCKMlkrA8Wad+QGe/Hfsds9u+fdl/f4f4WYNHQ2 +C6goJn2L76rUya0E7VDvWqfMVyV3RE7oiMPvglsCqFx1Dp8rtSEXt6Y3yQ5uMKZN +27El6ybLXZeMlTnVQQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq +hkiG9w0BAQUFAAOCAgEASHsNNYaBZ/+D2nHj7wliHU8SQfaXdFrJ1Ei5pGU0Laqb +z2rmtTeFjzq4bBNr6K6fjMEiT5SftTEOMpteCfWEzgHLEvkR9klEMD9es4BKoKkC +aCLLkKGv9135X+rZYWDaWMoksXNC61zVFziRExVa7HZSCiTXM9Hi6A0B9U+iCC4k +Y6era2G2yHSs1JCIWQlOHHLIfk79mtEwd84mfizYjx3Bj93ZTZXESFiRx/2/YbaI +Rr+g24n4zytWXaQb2ocb3HYmvTUkv1keJnaIGJZZFW9rePaRI3f6oVA15qsvwttz +F3UQ4ACpYUd0t2nUha+trFh3Srr2e3KQz8L/10gy6riLW/n2P6AmI6Ukmz/ChIoJ +ZydvbTdwJ0LCg89EFjUQThRszce66Cbd15awZjurfpQymgn1Sa2OXAzwUTuSc2Jr +x5+leiixvudRvfk83TQnabbbdlYY5EoJpjR7iqhgH7ONIl7xUn9WC2DXK99CeWQB +WX10OLXocksL+13IAYdAeLDDIaMvDInm8GZLhdlm+HRga2E30sVq9BwRywsLOP3C +3B2JOPLo4pZdAKOL7aaLI5kDOVuQ8NW/jCuc1JSrca1OSHHEtQ18YyYfsvcfzOdu +MlB8/TF5SzuMiXKo2k1wxDTwh7+ju2tZOc3jTgZCzLYNMn/ebYVivr/MIFwv4cw= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,50BDE04B335259A3D960185E8205D667 + +eeue1i1tlmzZy3QY21NNLy+m9Bh8BJY9+Bwx+pAC8L7mwSXZdMgESBw6ta41RVoR +DAzT746Bf9tvI7tRSW1XAdY+TV2cRBueIMwm3tkumAqfxVZhWxS2XHyG5lvX1Jik +UBWDNlSnqn8w/uE7jsxnAA4LphreseQoqvOp+WTL1sempOvVW+QyyltxfmTD1pAJ +U6omrTm9BO5LNv7Uri1sRRHTTE1ejXb8xJYt8YQQKjxn2JHap6I0X+nHQ8XGdTY4 +0O9TYGCnar0LHOvvWWAr/Z4DMf0YzjS8yptrJ+C2BIIZF+4ifrW8LrBmK5/X9LFI ++oAW9Tv8sdFYtpZpXheGgFP3dt9HhD7XgnQZ7d8+3hzHELAN+U0YyvWgnmktdBDp +6l0ifgexM121mGbG6JfyAhglKuqGwal+KttmAS4NL8m4ushzlsquUO6fHsYzXrHi +4+NcbJZ2udIn1MZymicztNBRCvGTjKYRgWwl8HG/pHdJ8i8w95xI+yYKwv46OG26 +unk3KrwSm/pc+Ztu+7WqPSjmOB8wsBGbXqaI4CyeM6Tj/5HPE6Mk69Fwk8moas4f +19eA9vzwnMKmyawLFTZ1TRZ3my4GIF1qWglIgtbux8B4Daq1DN9rTDXIroE8XJXL +5h9aHmI3vweslNYRTtLvkN1JCkY71FMv1QoK3FYoAknW0BFosDPG5fJXaiEMVd2c +avD5+CgTBg9T8n3InKR2D3Xae+4PxHlsxMa65GTq303hikcrjlYoEM6zUsb77SOt +W6wuzKPyJtDJzVFt6dIf6nkynxy9ouzRS3tx4XS6dCK+R9Xe0IPcYJiP/8/dmGUx +20qrf8RHCbbSJw8+ou9qT4QnJhExKIifSKmkpdzRxAnFz5p35Fl/wMUSL9QSQjW/ +jpOUEAtGVZsmR2wqmjQdrfLydwHAG1DnOuljHwpFuJ5QgQfXH+pvq7PmyVH0r4cM ++NdUUIOeKQXX0z3nVWIXhPyqUa6nPGRxF+KySspCae6ehkrXA3sV//955WL0YBF3 +v6JNBLN9FQnO86EfJsMvVN3X23tKb9j2DvmWITFqgJT4HJvBNLVfRUyxYW/9sAO6 +RUjoDq9nnwk7U2WVrCVuOEwx2D51AQebY2pB9uvxynC7sIF9AMQ4a+wIR5T7gM/V +m5hIC54ARWE3ReJRsRKxqygU1GE0OQLB6Zq2HNRUM6CyHFULoKp0EJfJE23u2B0S +6n2u+AFRHb37Db8Kv5pInbyPsij/eX6Pis3iOE+qa4Az3wZuYd3Y8Ouiu2SeSKbv +9ThjvkU2cvWcIEKYtSuH6JXTbFtyrnxT3djdYu8puPl6LjHsSCMZyPLN2JOLVpuk +rtVm9qhZwv3e6QoDRWrtJd4h3GOYolh+f6MIa3em/uLZCz816mAgRksuI53PI4Dv +OQG2MwZdFh2b9d+iegKwsTxq6SbULUD/aagEapM5Yn05JPaCUSdzjPnTSS0YDcps +Bf4RFW8MTPLxkYrc77EZwK8ncMs9xyY7YOfbA3+tYHeBy/NfQxGv70Dz8+gcLACY +6/1xAvD2pkhvCKal3KtJ8vjIhZURdLgc+dNaPStjTY2PwzGQcimucE1ksOh+WGX+ +L/4cYWYzSVD4NzOly+73Bf0cghs9lKUYHS3PA+GCwN4QgNFTRqNHDdJdd2IQjLPn +36ZKmv1Nby1ywxsWQ4vmxOwpN8tPEVoK6T1PsVh07fxnDpuy2dUsWTh+BxauLrfq +iCpLAyHsb9es+gjqWPUNnCPWDnVjUvwHcMIyfyR/Kq2KXI46uDNvKblVhV2MiZb+ +WukdXU91ZWbngd8YJTWQbedPvKDg/ifuAr7I5xpnhlr0GkdnV460l4+5am+N3wiS +UWl26tiivV8yFHIQtX0tmMUlGPDkTvYG71aa++MVZDg5kqQvSgBmxAs4Z8yUEU7N +d+hwKOIyEQVXALWLoKG+NZ4fga3yN6rY95qpsVrIJQbleSig034okc53rfVat1D6 +X9neZ1Ey8DBIheeqvwih6Xz/xxl2XruF+N6AViknagqfm9K9T2tilQt7IE253BYZ +OKwkMOfR4zEuRa8+hejImALEOHxKcCPoYzY1uXHNGGAE8jRHGyqsGXdIcifXQErw +NVY+UYOlVyGj3QPyUSmAYN51QUanJzd7PO3b8rdNOzX43RfyiKmECyMawUa3k505 +6Epa5TYNBx4rL7DHpoE4eqMogiVpgw6YHMNXjUvJFTqX+zI0AAADNI7U+M0YLoAs +b7Vlqji7cAIo8/HFyAY4Hmj2lq0D73sDd/LYB6dMY0br5qkhqVnINMBidN1U0oZc +YbdBPAMBqZkCtEUyfHeQ76HHyXqFYfiRZLzRD0r/2OtrH6VGYu4Iu41QjQbO8k3m +vAnTMh8xCEa+t+zQWu36iZkNoUnI+KtQTMHovYQ6sH6mHFNRSfx0ElLenqXpZ4xT +HdPIsgAdIZdFApQe+IAsdmcg0GvPYjs9AqjDuT91lXrnpRcPxTFjfH7nz/3aKaOO +7s0JPE9D5SSvl6zH7rN8yK6sXPSKojJFs0jHhn710yyoortkIej8usgkimDaYgrv +bOF1RNO0gi03aiqGvKI0pEOWpKrSAXONcqUniv1K+ttlGurgTyJZmHhwwLIFVdus +juD6PXkxjkX6L9R55+PLhaGOpBP9JSxb4gXGjX9Qbn6cuNEw7IBDssR0ifvxjXCu +wl1jbW0TaO3ol+C1f2/cKLXSzfnAF7WXf1mR6yUTn8cT8mS7yWaHJFoUDwUPA99l +9b18paZR+EwZtsR31pjijnDHwycsH75nsLTddVUc7yYptgmXtHuHyZY3FT7HixD7 +Zlwz30watxcBEopb7eq0ExSY4Ri+L9rV5gBYRzazlBM7J3xN1K8sAdkReclx5ms4 +1Ez/hv7m2xkNysPpRO8SCGoMjr/UxYL+kFI/1Y/DvvSx1EpX9SkoaAF5YB7Z89+N +uTD7PFVmqS0ZFUcXvrxGi1TGEvCWHsA6NY1wDNfzI7FiZCx1E0YjTWGjkdw5d/b3 +eCfhBCAQHJHC2/PX4s8MdgyAB/jatTjglCjAYPXO7PZqyOe1RyP7pxhd2eqEbJH3 +6/keQNj+O/TdUG47LUI2D/uZ92hNRenvuu5cMhRrUivBYhiw0X3w0bidGFSw5O/h +-----END RSA PRIVATE KEY----- diff --git a/test/gemfiles/chef-12.1.gemfile b/test/gemfiles/chef-12.1.gemfile new file mode 100644 index 0000000..83ee246 --- /dev/null +++ b/test/gemfiles/chef-12.1.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.1.2' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.10.gemfile b/test/gemfiles/chef-12.10.gemfile new file mode 100644 index 0000000..b3f1bce --- /dev/null +++ b/test/gemfiles/chef-12.10.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.10.24' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.11.gemfile b/test/gemfiles/chef-12.11.gemfile new file mode 100644 index 0000000..e1a1b77 --- /dev/null +++ b/test/gemfiles/chef-12.11.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.11.18' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.12.gemfile b/test/gemfiles/chef-12.12.gemfile new file mode 100644 index 0000000..cdbf048 --- /dev/null +++ b/test/gemfiles/chef-12.12.gemfile @@ -0,0 +1,23 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.12.15' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' diff --git a/test/gemfiles/chef-12.13.gemfile b/test/gemfiles/chef-12.13.gemfile new file mode 100644 index 0000000..76b2217 --- /dev/null +++ b/test/gemfiles/chef-12.13.gemfile @@ -0,0 +1,23 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.13.37' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' diff --git a/test/gemfiles/chef-12.14.gemfile b/test/gemfiles/chef-12.14.gemfile new file mode 100644 index 0000000..e0cb5c1 --- /dev/null +++ b/test/gemfiles/chef-12.14.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.14.89' diff --git a/test/gemfiles/chef-12.15.gemfile b/test/gemfiles/chef-12.15.gemfile new file mode 100644 index 0000000..1095dd8 --- /dev/null +++ b/test/gemfiles/chef-12.15.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.15.19' diff --git a/test/gemfiles/chef-12.16.gemfile b/test/gemfiles/chef-12.16.gemfile new file mode 100644 index 0000000..601a96c --- /dev/null +++ b/test/gemfiles/chef-12.16.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.16.42' diff --git a/test/gemfiles/chef-12.17.gemfile b/test/gemfiles/chef-12.17.gemfile new file mode 100644 index 0000000..2b2dbab --- /dev/null +++ b/test/gemfiles/chef-12.17.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.17.44' diff --git a/test/gemfiles/chef-12.18.gemfile b/test/gemfiles/chef-12.18.gemfile new file mode 100644 index 0000000..eef6f42 --- /dev/null +++ b/test/gemfiles/chef-12.18.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.18.31' diff --git a/test/gemfiles/chef-12.19.gemfile b/test/gemfiles/chef-12.19.gemfile new file mode 100644 index 0000000..62c4b98 --- /dev/null +++ b/test/gemfiles/chef-12.19.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.19.36' diff --git a/test/gemfiles/chef-12.2.gemfile b/test/gemfiles/chef-12.2.gemfile new file mode 100644 index 0000000..1be9dc1 --- /dev/null +++ b/test/gemfiles/chef-12.2.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.2.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.3.gemfile b/test/gemfiles/chef-12.3.gemfile new file mode 100644 index 0000000..f467b24 --- /dev/null +++ b/test/gemfiles/chef-12.3.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.3.0' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.4.gemfile b/test/gemfiles/chef-12.4.gemfile new file mode 100644 index 0000000..3982e37 --- /dev/null +++ b/test/gemfiles/chef-12.4.gemfile @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.4.3' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'gh', '0.14.0' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.5.gemfile b/test/gemfiles/chef-12.5.gemfile new file mode 100644 index 0000000..cce7846 --- /dev/null +++ b/test/gemfiles/chef-12.5.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.5.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.6.gemfile b/test/gemfiles/chef-12.6.gemfile new file mode 100644 index 0000000..5f699c5 --- /dev/null +++ b/test/gemfiles/chef-12.6.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.6.0' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.7.gemfile b/test/gemfiles/chef-12.7.gemfile new file mode 100644 index 0000000..8eb0df8 --- /dev/null +++ b/test/gemfiles/chef-12.7.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.7.2' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.8.gemfile b/test/gemfiles/chef-12.8.gemfile new file mode 100644 index 0000000..cf1531f --- /dev/null +++ b/test/gemfiles/chef-12.8.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.8.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.9.gemfile b/test/gemfiles/chef-12.9.gemfile new file mode 100644 index 0000000..4295ec5 --- /dev/null +++ b/test/gemfiles/chef-12.9.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.9.41' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.gemfile b/test/gemfiles/chef-12.gemfile new file mode 100644 index 0000000..bd22a0f --- /dev/null +++ b/test/gemfiles/chef-12.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.19' diff --git a/test/gemfiles/chef-13.0.gemfile b/test/gemfiles/chef-13.0.gemfile new file mode 100644 index 0000000..7e864da --- /dev/null +++ b/test/gemfiles/chef-13.0.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.0.118' diff --git a/test/gemfiles/chef-13.1.gemfile b/test/gemfiles/chef-13.1.gemfile new file mode 100644 index 0000000..05668a3 --- /dev/null +++ b/test/gemfiles/chef-13.1.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.1.31' diff --git a/test/gemfiles/chef-13.2.gemfile b/test/gemfiles/chef-13.2.gemfile new file mode 100644 index 0000000..a466fdb --- /dev/null +++ b/test/gemfiles/chef-13.2.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.2.20' diff --git a/test/gemfiles/chef-13.gemfile b/test/gemfiles/chef-13.gemfile new file mode 100644 index 0000000..3f49de1 --- /dev/null +++ b/test/gemfiles/chef-13.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.2' diff --git a/test/gemfiles/master.gemfile b/test/gemfiles/master.gemfile new file mode 100644 index 0000000..182ae64 --- /dev/null +++ b/test/gemfiles/master.gemfile @@ -0,0 +1,33 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', git: 'https://github.com/chef/chef.git' +gem 'chefspec', git: 'https://github.com/sethvargo/chefspec.git' +gem 'fauxhai', git: 'https://github.com/customink/fauxhai.git' +gem 'foodcritic', git: 'https://github.com/foodcritic/foodcritic.git' +gem 'halite', git: 'https://github.com/poise/halite.git' +gem 'ohai', git: 'https://github.com/chef/ohai.git' +gem 'poise', git: 'https://github.com/poise/poise.git' +gem 'poise-application', git: 'https://github.com/poise/application.git' +gem 'poise-application-git', git: 'https://github.com/poise/application_git.git' +gem 'poise-archive', git: 'https://github.com/poise/poise-archive.git' +gem 'poise-boiler', git: 'https://github.com/poise/poise-boiler.git' +gem 'poise-languages', git: 'https://github.com/poise/poise-languages.git' +gem 'poise-profiler', git: 'https://github.com/poise/poise-profiler.git' +gem 'poise-python', git: 'https://github.com/poise/poise-python.git' +gem 'poise-service', git: 'https://github.com/poise/poise-service.git' diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 0000000..7d56643 --- /dev/null +++ b/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,81 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'wsgi1' do + describe port(8000) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8000) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq "Hello world!\n" } + end +end + +describe 'wsgi1b' do + describe port(8001) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8001) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq "Hello world!\n" } + end +end + +describe 'wsgi2' do + describe port(8002) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8002) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include '/opt/wsgi2' } + its(:body) { is_expected.to include '/lib/python2.7' } + its(:body) { is_expected.to match %r'/(opt/rh|usr)/.*?/lib/python2.7/(site|dist)-packages' } + end +end + +describe 'wsgi3' do + describe port(8003) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8003) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include '/opt/wsgi3' } + its(:body) { is_expected.to include '/lib/python2.7' } + its(:body) { is_expected.to include '/opt/wsgi3/.virtualenv/lib/python2.7' } + its(:body) { is_expected.to_not match %r'/(opt/rh|usr)/.*?/lib/python2.7/(site|dist)-packages' } + end +end diff --git a/test/integration/default/serverspec/django_spec.rb b/test/integration/default/serverspec/django_spec.rb new file mode 100644 index 0000000..b9d8380 --- /dev/null +++ b/test/integration/default/serverspec/django_spec.rb @@ -0,0 +1,56 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'django' do + describe port(9000) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 9000) } + + describe '/foo' do + subject { http.get('/foo') } + its(:code) { is_expected.to eq '404' } + end + + describe '/admin/login/' do + subject { http.get('/admin/login/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'Polls Administration' } + end + + describe '/polls/' do + subject { http.get('/polls/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'No polls are available.' } + end + + describe '/static/polls/style.css' do + subject { http.get('/static/polls/style.css') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'color: green;' } + end + + describe '/static/polls/images/background.gif' do + subject { http.get('/static/polls/images/background.gif') } + its(:code) { is_expected.to eq '200' } + end +end diff --git a/test/integration/default/serverspec/flask_spec.rb b/test/integration/default/serverspec/flask_spec.rb new file mode 100644 index 0000000..8c0d0f2 --- /dev/null +++ b/test/integration/default/serverspec/flask_spec.rb @@ -0,0 +1,39 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'flask' do + describe port(9001) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 9001) } + + describe '/foo' do + subject { http.get('/foo') } + its(:code) { is_expected.to eq '404' } + end + + describe '/hi' do + subject { http.get('/hi') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq 'Hello World!' } + end +end diff --git a/test/spec/app_mixin_spec.rb b/test/spec/app_mixin_spec.rb new file mode 100644 index 0000000..2e34119 --- /dev/null +++ b/test/spec/app_mixin_spec.rb @@ -0,0 +1,69 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' +require 'poise_application/cheftie' +require 'poise_python/cheftie' + +describe PoiseApplicationPython::AppMixin do + describe '#parent_python' do + resource(:poise_test) do + include described_class + end + provider(:poise_test) + + context 'with an app_state python' do + recipe do + python_runtime 'outer' + application '/test' do + app_state[:python] = PoisePython::Resources::PythonRuntime::Resource.new('inner', run_context) + poise_test + end + python_runtime 'after' + poise_test 'after' + application '/other' + poise_test 'other' + end + let(:python) { chef_run.application('/test').app_state[:python] } + + it { is_expected.to run_poise_test('/test').with(parent_python: python) } + it { is_expected.to run_poise_test('after').with(parent_python: python) } + it { is_expected.to run_poise_test('other').with(parent_python: chef_run.python_runtime('after')) } + it { expect(python).to be_a Chef::Resource } + end # /context with an app_state python + + context 'with a global python' do + recipe do + python_runtime 'outer' + application '/test' do + poise_test + end + end + + it { is_expected.to run_poise_test('/test').with(parent_python: chef_run.python_runtime('outer')) } + end # /context with a global python + + context 'with no python' do + recipe do + application '/test' do + poise_test + end + end + + it { is_expected.to run_poise_test('/test').with(parent_python: nil) } + end # /context with no python + end # /describe #parent_python +end diff --git a/test/spec/resources/celery_config_spec.rb b/test/spec/resources/celery_config_spec.rb new file mode 100644 index 0000000..846c221 --- /dev/null +++ b/test/spec/resources/celery_config_spec.rb @@ -0,0 +1,58 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::CeleryConfig do + step_into(:application_celery_config) + before do + allow(File).to receive(:directory?).and_call_original + allow(File).to receive(:directory?).with('/test').and_return(true) + end + + context 'with defaults' do + recipe do + application_celery_config '/test' + end + it { is_expected.to deploy_application_celery_config('/test').with(path: '/test/celeryconfig.py') } + it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) } +# Generated by Chef for application_celery_config[/test] + +CELERYCONFIG + end # /context with defaults + + context 'with a specific path' do + recipe do + application_celery_config '/test/foo.py' + end + it { is_expected.to deploy_application_celery_config('/test/foo.py').with(path: '/test/foo.py') } + end # /context with a specific path + + context 'with template options' do + recipe do + application_celery_config '/test' do + options do + broker_url 'amqp://' + end + end + end + it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) } +# Generated by Chef for application_celery_config[/test] + +BROKER_URL = "amqp://" +CELERYCONFIG + end # /context with template options +end diff --git a/test/spec/resources/django_spec.rb b/test/spec/resources/django_spec.rb new file mode 100644 index 0000000..dccbd3c --- /dev/null +++ b/test/spec/resources/django_spec.rb @@ -0,0 +1,303 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Django do + describe PoiseApplicationPython::Resources::Django::Resource do + describe '#local_settings' do + subject { chef_run.application_django('/test').local_settings_content } + + context 'with defaults' do + recipe(subject: false) do + application_django '/test' + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with defaults + + context 'with a URL' do + recipe(subject: false) do + application_django '/test' do + database 'postgres://myuser@dbhost/myapp' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{"URL":"postgres://myuser@dbhost/myapp","ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}} +SETTINGS + end # /context with a URL + + context 'with an options block' do + recipe(subject: false) do + application_django '/test' do + database do + engine 'postgres' + name 'myapp' + user 'myuser' + host 'dbhost' + end + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{"ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}} +SETTINGS + end # /context with an options block + + context 'with debug mode' do + recipe(subject: false) do + application_django '/test' do + debug true + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = True + +DATABASES = {"default":{}} +SETTINGS + end # /context with debug mode + + context 'with a single allowed host' do + recipe(subject: false) do + application_django '/test' do + allowed_hosts 'example.com' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +ALLOWED_HOSTS = ["example.com"] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with a single allowed host + + context 'with multiple allowed hosts' do + recipe(subject: false) do + application_django '/test' do + allowed_hosts %w{example.com www.example.com} + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +ALLOWED_HOSTS = ["example.com","www.example.com"] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with multiple allowed hosts + + context 'with a secret key' do + recipe(subject: false) do + application_django '/test' do + secret_key 'swordfish' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{}} + +SECRET_KEY = "swordfish" +SETTINGS + end # /context with a secret key + end # /describe #local_settings + + describe '#default_local_settings_path' do + subject { chef_run.application_django('/test').send(:default_local_settings_path) } + + context 'with no settings.py' do + recipe(subject: false) do + application_django '/test' do + def settings_module + nil + end + end + end + it { is_expected.to be_nil } + end # /context with no settings.py + + context 'with basic settings.py' do + recipe(subject: false) do + application_django '/test' do + settings_module 'myapp.settings' + end + end + it { is_expected.to eq '/test/myapp/local_settings.py' } + end # /context with basic settings.py + end # /describe #default_local_settings_path + + describe '#default_manage_path' do + subject { chef_run.application_django('/test').send(:default_manage_path) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('manage.py').and_return('/test/manage.py') + end + + it { is_expected.to eq '/test/manage.py' } + end # /describe #default_manage_path + + describe '#default_settings_module' do + let(:settings_path) { nil } + subject { chef_run.application_django('/test').send(:default_settings_module) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('settings.py').and_return(settings_path) + end + + context 'with no settings.py' do + it { is_expected.to be_nil } + end # /context with no settings.py + + context 'with simple settings.py' do + let(:settings_path) { '/test/myapp/settings.py' } + it { is_expected.to eq 'myapp.settings' } + end # /context with simple settings.py + end # /describe #default_settings_module + + describe '#default_wsgi_module' do + let(:wsgi_path) { nil } + subject { chef_run.application_django('/test').send(:default_wsgi_module) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('wsgi.py').and_return(wsgi_path) + end + + context 'with no wsgi.py' do + it { is_expected.to be_nil } + end # /context with no wsgi.py + + context 'with simple wsgi.py' do + let(:wsgi_path) { '/test/wsgi.py' } + it { is_expected.to eq 'wsgi' } + end # /context with simple wsgi.py + end # /describe #default_wsgi_module + + describe '#find_file' do + let(:files) { [] } + recipe(subject: false) do + application_django '/test' + end + subject { chef_run.application_django('/test').send(:find_file, 'myfile.py') } + before do + allow(Dir).to receive(:[]).and_call_original + allow(Dir).to receive(:[]).with('/test/**/myfile.py').and_return(files) + end + + context 'with no matching files' do + it { is_expected.to be_nil } + end # /context with no matching files + + context 'with one matching file' do + let(:files) { %w{/test/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with one matching file + + context 'with two matching files' do + let(:files) { %w{/test/myfile.py /test/sub/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with two matching files + + context 'with two matching files in a different order' do + let(:files) { %w{/test/sub/myfile.py /test/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with two matching files in a different order + + context 'with two matching files on the same level' do + let(:files) { %w{/test/b/myfile.py /test/a/myfile.py} } + it { is_expected.to eq '/test/a/myfile.py' } + end # /context with two matching files on the same level + end # /describe #find_file + end # /describe PoiseApplicationPython::Resources::Django::Resource + + describe PoiseApplicationPython::Resources::Django::Provider do + step_into(:application_django) + context 'with default settings' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + settings_module 'myapp.settings' + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to_not run_python_execute('manage.py migrate --noinput') } + it { is_expected.to render_file('/test/myapp/local_settings.py') } + end # /context with default settings + + context 'with syncdb' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + settings_module 'myapp.settings' + syncdb true + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to_not run_python_execute('manage.py migrate --noinput') } + end # /context with syncdb + + context 'with migrate' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + migrate true + settings_module 'myapp.settings' + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to run_python_execute('manage.py migrate --noinput') } + it { is_expected.to render_file('/test/myapp/local_settings.py') } + end # /context with migrate + end # /describe PoiseApplicationPython::Resources::Django::Provider +end diff --git a/test/spec/resources/gunicorn_spec.rb b/test/spec/resources/gunicorn_spec.rb new file mode 100644 index 0000000..c50da3b --- /dev/null +++ b/test/spec/resources/gunicorn_spec.rb @@ -0,0 +1,96 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Gunicorn do + describe PoiseApplicationPython::Resources::Gunicorn::Resource do + describe '#default_app_module' do + let(:app_state) { {} } + let(:files) { [] } + let(:test_resource) { described_class.new('/test', nil) } + before do + allow(test_resource).to receive(:app_state).and_return(app_state) + allow(Dir).to receive(:exist?).and_return(!files.empty?) + allow(Dir).to receive(:entries).and_return(files) + end + subject { test_resource.send(:default_app_module) } + + context 'with an app_state key' do + let(:app_state) { {python_wsgi_module: 'django'} } + it { is_expected.to eq 'django' } + end # /context with an app_state key + + context 'with a wsgi.py' do + let(:files) { %w{wsgi.py} } + it { is_expected.to eq 'wsgi' } + end # /context with a wsgi.py + + context 'with an app.py and main.py' do + let(:files) { %w{app.py main.py} } + it { is_expected.to eq 'main' } + end # /context with an app.py and main.py + + context 'with a foo.txt and bar.py' do + let(:files) { %w{foo.txt bar.py} } + it { is_expected.to eq 'bar' } + end # /context with a foo.txt and bar.py + + context 'with a foo.txt' do + let(:files) { %w{foo.txt } } + it { is_expected.to be_nil } + end # /context with a foo.txt + end # /describe #default_app_module + end # /describe PoiseApplicationPython::Resources::Gunicorn::Resource + + describe PoiseApplicationPython::Resources::Gunicorn::Provider do + let(:new_resource) { double('new_resource') } + let(:test_provider) { described_class.new(new_resource, nil) } + + describe '#gunicorn_command_options' do + let(:props) { {} } + let(:new_resource) { PoiseApplicationPython::Resources::Gunicorn::Resource.new('/test', nil) } + subject { test_provider.send(:gunicorn_command_options).join(' ') } + before do + props.each {|key, value| new_resource.send(key, value) } + end + + context 'with defaults' do + it { is_expected.to eq '--bind 0.0.0.0:80' } + end # /context with defaults + + context 'with a config file' do + let(:props) { {config: '/test/myconfig.py'} } + it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80' } + end # /context with a config file + + context 'with a blank config file' do + let(:props) { {config: ''} } + it { is_expected.to eq '--bind 0.0.0.0:80' } + end # /context with a blank config file + + context 'with two binds' do + let(:props) { {bind: %w{0.0.0.0:80 0.0.0.0:81}} } + it { is_expected.to eq '--bind 0.0.0.0:80 --bind 0.0.0.0:81' } + end # /context with two binds + + context 'with a config file and preload' do + let(:props) { {config: '/test/myconfig.py', preload_app: true} } + it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80 --preload' } + end # /context with a config file and preload + end # /describe #gunicorn_command_options + end # /describe PoiseApplicationPython::Resources::Gunicorn::Provider +end diff --git a/test/spec/resources/python_execute_spec.rb b/test/spec/resources/python_execute_spec.rb new file mode 100644 index 0000000..29107f8 --- /dev/null +++ b/test/spec/resources/python_execute_spec.rb @@ -0,0 +1,52 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::PythonExecute do + step_into(:application_python_execute) + recipe do + application '/srv/myapp' do + owner 'myuser' + group 'mygroup' + environment ENVKEY: 'ENVVALUE' + + python('') { provider :dummy } + python_execute 'myapp.py' + end + end + + it do + # Check which method to stub. I'm not super proud of this code, sorry. + method_to_stub = if IO.read(described_class::Provider.instance_method(:action_run).source_location.first) =~ /shell_out_with_systems_locale/ + :shell_out_with_systems_locale! + else + :shell_out! + end + expect_any_instance_of(described_class::Provider).to receive(method_to_stub).with( + '/python myapp.py', + user: 'myuser', + group: 'mygroup', + cwd: '/srv/myapp', + timeout: 3600, + returns: 0, + environment: {'ENVKEY' => 'ENVVALUE'}, + log_level: :info, + log_tag: 'application_python_execute[myapp.py]', + ) + is_expected.to run_application_python_execute('myapp.py').with(user: 'myuser', group: 'mygroup', cwd: '/srv/myapp') + end +end diff --git a/test/spec/resources/python_spec.rb b/test/spec/resources/python_spec.rb new file mode 100644 index 0000000..6e5ff3c --- /dev/null +++ b/test/spec/resources/python_spec.rb @@ -0,0 +1,44 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Python do + shared_examples 'application_python' do + it { is_expected.to install_application_python('ver') } + it { expect(chef_run.application('/test').app_state[:python]).to eq chef_run.application_python('ver') } + end # /shared_examples application_python + + context 'with #python_runtime' do + recipe do + application '/test' do + python_runtime 'ver' + end + end + + it_behaves_like 'application_python' + end # /context with #python_runtime + + context 'with #python' do + recipe do + application '/test' do + python 'ver' + end + end + + it_behaves_like 'application_python' + end # /context with #python +end diff --git a/test/spec/resources/virtualenv_spec.rb b/test/spec/resources/virtualenv_spec.rb new file mode 100644 index 0000000..082d2e7 --- /dev/null +++ b/test/spec/resources/virtualenv_spec.rb @@ -0,0 +1,44 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Virtualenv do + shared_examples 'application_virtualenv' do + it { is_expected.to create_application_virtualenv('/test').with(path: '/test/.virtualenv') } + it { expect(chef_run.application('/test').app_state[:python]).to eq chef_run.application_virtualenv('/test') } + end # /shared_examples application_virtualenv + + context 'with #python_virtualenv' do + recipe do + application '/test' do + python_virtualenv + end + end + + it_behaves_like 'application_virtualenv' + end # /context with #python_virtualenv + + context 'with #virtualenv' do + recipe do + application '/test' do + virtualenv + end + end + + it_behaves_like 'application_virtualenv' + end # /context with #virtualenv +end diff --git a/test/spec/spec_helper.rb b/test/spec/spec_helper.rb new file mode 100644 index 0000000..2ed212f --- /dev/null +++ b/test/spec/spec_helper.rb @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# 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 +# +# 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. +# + +require 'poise_boiler/spec_helper' +require 'poise_application_python' +require 'poise_application/cheftie'