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 4044237..f2b213a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,64 +1,71 @@ -## v1.2.4: +# Application_Python Changelog -### Bug +## v4.0.0 -- [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` +* Massive rewrite on top of newer Chef patterns. See the 4.0 README for details. -## v1.2.2: +## v3.0.0 -### 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-2796]: celery provider tries to case switch on 'queue' - parameter instead of 'queues' parameter +## v2.0.4 -## v1.2.0: +* Revert changes that broke backwards compatibility. -### Improvement +## v2.0.2 -- [COOK-2611]: Celery LWRP should configure which queues a celeryd - worker binds to +* **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. -### Bug +## v2.0.0 -- [COOK-2599]: gunicorn provider fails if no `node['cpu']['total']` +* [COOK-3306]: Multiple Memory Leaks in Application Cookbook. -## v1.1.0: +## 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 * [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-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. -## v1.0.8: +## v1.0.8 -* [COOK-2175] - Template cookbook attribute expecting a string, -getting symbol instead. +* [COOK-2175] - Template cookbook attribute expecting a stringg getting symbol instead. -## v1.0.6: +## v1.0.6 -* [COOK-2122] - pip was incorrectly using -E syntax -* [COOK-2147] - django sub-resource searched wrong directory for - requirements.txt +* [COOK-2122] - pip was incorrectly using -E syntax. +* [COOK-2147] - django sub-resource searched wrong directory for requirements.txt. -## v1.0.4: +## v1.0.4 -* [COOK-2042] - gunicorn LWRP support for virtualenv, deps +* [COOK-2042] - gunicorn LWRP support for virtualenv, deps. -## v1.0.2: +## 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 +* [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 90084ad..74ed6f7 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,332 @@ -Description -=========== +# Application_Python Cookbook -This cookbook is designed to be able to describe and deploy Python web applications. Currently supported: +[![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) -* plain python web applications -* Django -* Green Unicorn -* Celery +A [Chef](https://www.chef.io/) cookbook to deploy Python applications. -Note that this cookbook provides the Python-specific bindings for the `application` cookbook; you will find general documentation in that cookbook. +## Quick Start -Other application stacks may be supported at a later date. +To deploy a Django application from git: -Requirements -============ +```ruby +application '/srv/myapp' do + git 'https://github.com/example/myapp.git' + virtualenv + pip_requirements + django do + database 'sqlite:///test_django.db' + secret_key 'd78fe08df56c9' + migrate true + end + gunicorn do + port 8000 + end +end +``` -Chef 0.10.0 or higher required (for Chef environment use). +## Requirements -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`. - -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 "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 - end +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` -You can invoke any method on the database block: - - application "my-app" do - path "/srv/packaginator" - repository "..." - revision "..." - - django do - database_master_role "packaginator_database_master" - database do - database 'name' - quorum 2 - replicas %w[Huey Dewey Louie] - end - end +The `application_celery_config` creates a `celeryconfig.py` configuration file. + +```ruby +application '/srv/myapp' do + celery_config do + options do + broker_url 'amqp://' end + end +end +``` + +#### 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 '/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 'sqlite:///test_django.db' + migrate true + end +end +``` + +#### 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`: + +```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. + +As with other option collector resources, you can pass individual settings as +either a hash or block: + +```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) -The corresponding entries will be passed to the context template: +## Sponsors - <%= @database['quorum'] - <%= @database['replicas'].join(',') %> +Development sponsored by [Chef Software](https://www.chef.io/), [Symonds & Son](http://symondsandson.com/), and [Orion](https://www.orionlabs.co/). -License and Author -================== +The Poise test server infrastructure is sponsored by [Rackspace](https://rackspace.com/). -Author:: Adam Jacob () -Author:: Andrea Campi () -Author:: Joshua Timberman () -Author:: Noah Kantrowitz () -Author:: Seth Chisamore () +## License -Copyright 2009-2013, Opscode, Inc. +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, 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 cd756d1..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 af6fe4b..0000000 --- a/metadata.rb +++ /dev/null @@ -1,11 +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 "1.2.5" - -%w{ application python gunicorn supervisor }.each do |cb| - depends cb -end 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 f33b3d5..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::Mixin::LanguageIncludeRecipe - -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 bf66e59..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::Mixin::LanguageIncludeRecipe - -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 3668cdf..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::Mixin::LanguageIncludeRecipe - -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 false - 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 9166b8d..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 Chef::Resource::ApplicationBase - -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 7282df5..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 Chef::Resource::ApplicationBase - -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 d1e736e..0000000 --- a/resources/gunicorn.rb +++ /dev/null @@ -1,48 +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 Chef::Resource::ApplicationBase - -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 :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'