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 313da6f..f2b213a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,83 +1,71 @@ -application_python Cookbok CHANGELOG -==================================== -This file is used to list changes made in each version of the application_python cookbook. +# Application_Python Changelog +## v4.0.0 -v3.0.0 ------- -### Breaking -- Drop support for Chef 10 +* Massive rewrite on top of newer Chef patterns. See the 4.0 README for details. -### Bug -- **[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 +## v3.0.0 -v2.0.4 ------- -- Revery changes that broke backwards compatability +* 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. -v2.0.2 ------- -**This release did not follow semver and was reverted in 2.0.4!** +## v2.0.4 -### Bug -- **[COOK-3432](https://tickets.opscode.com/browse/COOK-3432)** - Use `Chef::DSL::IncludeRecipe` because `Chef::Mixin::LanguageIncludeRecipe` is deprecated +* Revert changes that broke backwards compatibility. -v2.0.0 ------- -### Bug -- [COOK-3306]: Multiple Memory Leaks in Application Cookbook +## v2.0.2 -v1.2.4 ------- -### Bug +* **This release did not follow semver and was reverted in 2.0.4!** +* [COOK-3432](https://tickets.opscode.com/browse/COOK-3432) - Use `Chef::DSL::IncludeRecipe` because `Chef::Mixin::LanguageIncludeRecipe` is deprecated. -- [COOK-2747]: celerycam configuration is not suitable for multiple node celery installation -- [COOK-2766]: pip does not use `deploy_key` in django ressource of `application_python` +## v2.0.0 -v1.2.2 ------- -### Bug -- [COOK-2796]: celery provider tries to case switch on 'queue' parameter instead of 'queues' parameter +* [COOK-3306]: Multiple Memory Leaks in Application Cookbook. -v1.2.0 ------- -### Improvement -- [COOK-2611]: Celery LWRP should configure which queues a celeryd worker binds to +## v1.2.4 -### Bug +* [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`. -- [COOK-2599]: gunicorn provider fails if no `node['cpu']['total']` +## v1.2.2 + +* [COOK-2796]: celery provider tries to case switch on 'queue' parameter instead of 'queues' parameter. + +## v1.2.0 + +* [COOK-2611]: Celery LWRP should configure which queues a celeryd worker binds to. +* [COOK-2599]: gunicorn provider fails if no `node['cpu']['total']`. + +## v1.1.0 -v1.1.0 ------- * [COOK-2330] - celeryconfig.py.erb tries to use non-existant String#upper method * [COOK-2337] - It should be possible to pass environment variables through to gunicorn and celery supervisor configs -* [COOK-2403] - cookbook attribute expects argument to be a string -* [COOK-2453] - application_python should allow the working directory of gunicorn processes to be set via an attribute -* [COOK-2475] - celerybeat supervisor process is unnecessarily configured -* [COOK-2484] - virtualenv and requirements are installed as root instead of uid/gid specified by application properties +* [COOK-2403] - cookbook attribute expects argument to be a string. +* [COOK-2453] - application_python should allow the working directory of gunicorn processes to be set via an attribute. +* [COOK-2475] - celerybeat supervisor process is unnecessarily configured. +* [COOK-2484] - virtualenv and requirements are installed as root instead of uid/gid specified by application properties. + +## v1.0.8 -v1.0.8 ------- * [COOK-2175] - Template cookbook attribute expecting a stringg getting symbol instead. -v1.0.6 ------- -* [COOK-2122] - pip was incorrectly using -E syntax -* [COOK-2147] - django sub-resource searched wrong directory for requirements.txt +## v1.0.6 + +* [COOK-2122] - pip was incorrectly using -E syntax. +* [COOK-2147] - django sub-resource searched wrong directory for requirements.txt. + +## v1.0.4 + +* [COOK-2042] - gunicorn LWRP support for virtualenv, deps. + +## v1.0.2 -v1.0.4 ------- -* [COOK-2042] - gunicorn LWRP support for virtualenv, deps +* [COOK-1420] - template resource source cookbook is wrong. +* [COOK-1421] - pip using old -E syntax. +* [COOK-1422] - syncdb using --migrate option. +* [COOK-1477] - pip requirements.txt and editable package support. -v1.0.2 ------- -* [COOK-1420] - template resource source cookbook is wrong -* [COOK-1421] - pip using old -E syntax -* [COOK-1422] - syncdb using --migrate option -* [COOK-1477] - pip requirements.txt and editable package support +## v1.0.0 -v1.0.0 ------- * [COOK-1246] - Initial release - relates to COOK-634. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 3a99897..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,257 +0,0 @@ -# Contributing to Opscode Cookbooks - -We are glad you want to contribute to Opscode Cookbooks! The first -step is the desire to improve the project. - -You can find the answers to additional frequently asked questions -[on the wiki](http://wiki.opscode.com/display/chef/How+to+Contribute). - -You can find additional information about -[contributing to cookbooks](http://wiki.opscode.com/display/chef/How+to+Contribute+to+Opscode+Cookbooks) -on the wiki as well. - -## Quick-contribute - -* Create an account on our [bug tracker](http://tickets.opscode.com) -* Sign our contributor agreement (CLA) -[ online](https://secure.echosign.com/public/hostedForm?formid=PJIF5694K6L) -(keep reading if you're contributing on behalf of your employer) -* Create a ticket for your change on the - [bug tracker](http://tickets.opscode.com) -* Link to your patch as a rebased git branch or pull request from the - ticket -* Resolve the ticket as fixed - -We regularly review contributions and will get back to you if we have -any suggestions or concerns. - -## The Apache License and the CLA/CCLA - -Licensing is very important to open source projects, it helps ensure -the software continues to be available under the terms that the author -desired. Chef uses the Apache 2.0 license to strike a balance between -open contribution and allowing you to use the software however you -would like to. - -The license tells you what rights you have that are provided by the -copyright holder. It is important that the contributor fully -understands what rights they are licensing and agrees to them. -Sometimes the copyright holder isn't the contributor, most often when -the contributor is doing work for a company. - -To make a good faith effort to ensure these criteria are met, Opscode -requires a Contributor License Agreement (CLA) or a Corporate -Contributor License Agreement (CCLA) for all contributions. This is -without exception due to some matters not being related to copyright -and to avoid having to continually check with our lawyers about small -patches. - -It only takes a few minutes to complete a CLA, and you retain the -copyright to your contribution. - -You can complete our contributor agreement (CLA) -[ online](https://secure.echosign.com/public/hostedForm?formid=PJIF5694K6L). -If you're contributing on behalf of your employer, have your employer -fill out our -[Corporate CLA](https://secure.echosign.com/public/hostedForm?formid=PIE6C7AX856) -instead. - -## Ticket Tracker (JIRA) - -The [ticket tracker](http://tickets.opscode.com) is the most important -documentation for the code base. It provides significant historical -information, such as: - -* Which release a bug fix is included in -* Discussion regarding the design and merits of features -* Error output to aid in finding similar bugs - -Each ticket should aim to fix one bug or add one feature. - -## Using git - -You can get a quick copy of the repository for this cookbook by -running `git clone -git://github.com/opscode-coobkooks/COOKBOOKNAME.git`. - -For collaboration purposes, it is best if you create a Github account -and fork the repository to your own account. Once you do this you will -be able to push your changes to your Github repository for others to -see and use. - -If you have another repository in your GitHub account named the same -as the cookbook, we suggest you suffix the repository with -cookbook. - -### Branches and Commits - -You should submit your patch as a git branch named after the ticket, -such as COOK-1337. This is called a _topic branch_ and allows users to -associate a branch of code with the ticket. - -It is a best practice to have your commit message have a _summary -line_ that includes the ticket number, followed by an empty line and -then a brief description of the commit. This also helps other -contributors understand the purpose of changes to the code. - - [COOK-1757] - platform_family and style - - * use platform_family for platform checking - * update notifies syntax to "resource_type[resource_name]" instead of - resources() lookup - * COOK-692 - delete config files dropped off by packages in conf.d - * dropped debian 4 support because all other platforms have the same - values, and it is older than "old stable" debian release - -Remember that not all users use Chef in the same way or on the same -operating systems as you, so it is helpful to be clear about your use -case and change so they can understand it even when it doesn't apply -to them. - -### Github and Pull Requests - -All of Opscode's open source cookbook projects are available on -[Github](http://www.github.com/opscode-cookbooks). - -We don't require you to use Github, and we will even take patch diffs -attached to tickets on the tracker. However Github has a lot of -convenient features, such as being able to see a diff of changes -between a pull request and the main repository quickly without -downloading the branch. - -If you do choose to use a pull request, please provide a link to the -pull request from the ticket __and__ a link to the ticket from the -pull request. Because pull requests only have two states, open and -closed, we can't easily filter pull requests that are waiting for a -reply from the author for various reasons. - -### More information - -Additional help with git is available on the -[Working with Git](http://wiki.opscode.com/display/chef/Working+with+Git) -wiki page. - -## Functional and Unit Tests - -This cookbook is set up to run tests under -[Opscode's test-kitchen](https://github.com/opscode/test-kitchen). It -uses minitest-chef to run integration tests after the node has been -converged to verify that the state of the node. - -Test kitchen should run completely without exception using the default -[baseboxes provided by Opscode](https://github.com/opscode/bento). -Because Test Kitchen creates VirtualBox machines and runs through -every configuration in the Kitchenfile, it may take some time for -these tests to complete. - -If your changes are only for a specific recipe, run only its -configuration with Test Kitchen. If you are adding a new recipe, or -other functionality such as a LWRP or definition, please add -appropriate tests and ensure they run with Test Kitchen. - -If any don't pass, investigate them before submitting your patch. - -Any new feature should have unit tests included with the patch with -good code coverage to help protect it from future changes. Similarly, -patches that fix a bug or regression should have a _regression test_. -Simply put, this is a test that would fail without your patch but -passes with it. The goal is to ensure this bug doesn't regress in the -future. Consider a regular expression that doesn't match a certain -pattern that it should, so you provide a patch and a test to ensure -that the part of the code that uses this regular expression works as -expected. Later another contributor may modify this regular expression -in a way that breaks your use cases. The test you wrote will fail, -signalling to them to research your ticket and use case and accounting -for it. - -If you need help writing tests, please ask on the Chef Developer's -mailing list, or the #chef-hacking IRC channel. - -## Code Review - -Opscode regularly reviews code contributions and provides suggestions -for improvement in the code itself or the implementation. - -We find contributions by searching the ticket tracker for _resolved_ -tickets with a status of _fixed_. If we have feedback we will reopen -the ticket and you should resolve it again when you've made the -changes or have a response to our feedback. When we believe the patch -is ready to be merged, we will tag the _Code Reviewed_ field with -_Reviewed_. - -Depending on the project, these tickets are then merged within a week -or two, depending on the current release cycle. - -## Release Cycle - -The versioning for Opscode Cookbook projects is X.Y.Z. - -* X is a major release, which may not be fully compatible with prior - major releases -* Y is a minor release, which adds both new features and bug fixes -* Z is a patch release, which adds just bug fixes - -A released version of a cookbook will end in an even number, e.g. -"1.2.4" or "0.8.0". When development for the next version of the -cookbook begins, the "Z" patch number is incremented to the next odd -number, however the next release of the cookbook may be a major or -minor incrementing version. - -Releases of Opscode's cookbooks are usually announced on the Chef user -mailing list. Releases of several cookbooks may be batched together -and announced on the [Opscode Blog](http://www.opscode.com/blog). - -## Working with the community - -These resources will help you learn more about Chef and connect to -other members of the Chef community: - -* [chef](http://lists.opscode.com/sympa/info/chef) and - [chef-dev](http://lists.opscode.com/sympa/info/chef-dev) mailing - lists -* #chef and #chef-hacking IRC channels on irc.freenode.net -* [Community Cookbook site](http://community.opscode.com) -* [Chef wiki](http://wiki.opscode.com/display/chef) -* Opscode Chef [product page](http://www.opscode.com/chef) - - -## Cookbook Contribution Do's and Don't's - -Please do include tests for your contribution. If you need help, ask -on the -[chef-dev mailing list](http://lists.opscode.com/sympa/info/chef-dev) -or the -[#chef-hacking IRC channel](http://community.opscode.com/chat/chef-hacking). -Not all platforms that a cookbook supports may be supported by Test -Kitchen. Please provide evidence of testing your contribution if it -isn't trivial so we don't have to duplicate effort in testing. Chef -10.14+ "doc" formatted output is sufficient. - -Please do indicate new platform (families) or platform versions in the -commit message, and update the relevant ticket. - -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.' - -Please do use [foodcritic](http://acrmp.github.com/foodcritic) to -lint-check the cookbook. Except FC007, it should pass all correctness -rules. FC007 is okay as long as the dependent cookbooks are *required* -for the default behavior of the cookbook, such as to support an -uncommon platform, secondary recipe, etc. - -Please do ensure that your changes do not break or modify behavior for -other platforms supported by the cookbook. For example if your changes -are for Debian, make sure that they do not break on CentOS. - -Please do not modify the version number in the metadata.rb, Opscode -will select the appropriate version based on the release cycle -information above. - -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 update the CHANGELOG.md when releasing a new version of -the cookbook. 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 c9cb523..74ed6f7 100644 --- a/README.md +++ b/README.md @@ -1,178 +1,335 @@ -application_python Cookbook -=========================== -This cookbook is designed to be able to describe and deploy Python web applications. Currently supported: +# Application_Python Cookbook -- plain python web applications -- Django -- Green Unicorn -- Celery +[![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) -Note that this cookbook provides the Python-specific bindings for the `application` cookbook; you will find general documentation in that cookbook. +A [Chef](https://www.chef.io/) cookbook to deploy Python applications. -Other application stacks may be supported at a later date. +## Quick Start +To deploy a Django application from git: -Requirements ------------- -Chef 0.10.0 or higher required (for Chef environment use). +```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 +``` -The following Opscode cookbooks are dependencies: +## Requirements -- application -- python -- gunicorn -- supervisor +Chef 12.1 or newer is required. +## Resources -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. +### `application_celery_beat` +The `application_celery_beat` resource creates a service for the `celery beat` +process. -### 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. +```ruby +application '/srv/myapp' do + celery_beat do + app_module 'myapp.tasks' + end +end +``` -A new virtualenv will be created for the application in "#{path}/shared/env"; pip package will be installed in that virtualenv. +#### Actions -#### 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 +* `: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. -#### Database block parameters +#### Properties -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. +* `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)* -### gunicorn -The `gunicorn` sub-resource LWRP configures Green Unicorn to run the application. +### `application_celery_config` -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}`. +The `application_celery_config` creates a `celeryconfig.py` configuration file. -#### Attribute Parameters +```ruby +application '/srv/myapp' do + celery_config do + options do + broker_url 'amqp://' + end + end +end +``` -- app_module: mandatory. If set to :django, gunicorn will be configured to run a Django application; if set to another String or Symbol, it will be used to build the gunicorn base command. -- settings_template: the template to render to create the `gunicorn_config.py` file; if specified it will be looked up in the application cookbook. Defaults to "se.py.erb" from the `gunicorn` cookbook -- host: passed to the `gunicorn_config` LWRP -- port: passed to the `gunicorn_config` LWRP -- backlog: passed to the `gunicorn_config` LWRP -- workers: passed to the `gunicorn_config` LWRP -- worker_class: passed to the `gunicorn_config` LWRP -- worker_connections: passed to the `gunicorn_config` LWRP -- max_requests: passed to the `gunicorn_config` LWRP -- timeout: passed to the `gunicorn_config` LWRP -- keepalive: passed to the `gunicorn_config` LWRP -- debug: passed to the `gunicorn_config` LWRP -- trace: passed to the `gunicorn_config` LWRP -- preload_app: passed to the `gunicorn_config` LWRP -- daemon: passed to the `gunicorn_config` LWRP -- pidfile: passed to the `gunicorn_config` LWRP -- umask: passed to the `gunicorn_config` LWRP -- logfile: passed to the `gunicorn_config` LWRP -- loglevel: passed to the `gunicorn_config` LWRP -- proc_name: passed to the `gunicorn_config` LWRP -- environment: hash of environment variables passed to `supervisor_service` -- autostart: passed to `supervisor_service`. +#### Actions +* `:deploy` – Create the configuration file. *(default)* -### celery -The `celery` sub resource LWRP configures the application to use Celery. +#### Properties -#### 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`. +* `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` -Usage ------ -A sample application that needs a database connection: +The `application_celery_worker` resource creates a service for the +`celery worker` process. ```ruby -application 'packaginator' do - path '/srv/packaginator' - owner 'nobody' - group 'nogroup' - repository 'https://github.com/coderanger/packaginator.git' - revision 'master' - migrate true - packages ['libpq-dev', 'git-core', 'mercurial'] +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 - packages ['redis'] - requirements 'requirements/mkii.txt' - settings_template 'settings.py.erb' - debug true - collectstatic 'build_static --noinput' - database do - database 'packaginator' - adapter 'postgresql_psycopg2' - username 'packaginator' - password 'awesome_password' - end - database_master_role 'packaginator_database_master' + database 'sqlite:///test_django.db' + migrate true end end ``` -You can invoke any method on the database block: +#### 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 -application 'my-app' do - path '/srv/packaginator' - repository '...' - revision '...' +django do + database 'postgres://myuser@dbhost/myapp' +end +``` - django do - database_master_role 'packaginator_database_master' - database do - database 'name' - quorum 2 - replicas ['Huey', 'Dewey', 'Louie'] - 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 ``` -The corresponding entries will be passed to the context template: +### `application_gunicorn` + +The `application_gunicorn` resource creates a service for the +[Gunicorn](http://gunicorn.org/) application server. -```erb -<%= @database['quorum'] %> -<%= @database['replicas'].join(',') %> +```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 -License & Authors ------------------ -- Author:: Adam Jacob () -- Author:: Andrea Campi () -- Author:: Joshua Timberman () -- Author:: Noah Kantrowitz () -- Author:: Seth Chisamore () +* `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)* -```text -Copyright 2009-2013, Opscode, Inc. +### `application_pip_requirements` + +The `application_pip_requirements` resource installs Python packages based on a +`requirements.txt` file. + +```ruby +application '/srv/myapp' do + pip_requirements +end +``` + +All actions and properties are the same as the [`pip_requirements` resource](https://github.com/poise/poise-python#pip_requirements). + +### `application_python` + +The `application_python` resource installs a Python runtime for the deployment. + +```ruby +application '/srv/myapp' do + python '2.7' +end +``` + +All actions and properties are the same as the [`python_runtime` resource](https://github.com/poise/poise-python#python_runtime). + +### `application_python_execute` + +The `application_python_execute` resource runs Python commands for the deployment. + +```ruby +application '/srv/myapp' do + python_execute 'setup.py install' +end +``` + +All actions and properties are the same as the [`python_execute` resource](https://github.com/poise/poise-python#python_execute), +except that the `cwd`, `environment`, `group`, and `user` properties default to +the application-level data if not specified. + +### `application_python_package` + +The `application_python_package` resource installs Python packages for the deployment. + +```ruby +application '/srv/myapp' do + python_package 'requests' +end +``` + +All actions and properties are the same as the [`python_package` resource](https://github.com/poise/poise-python#python_package), +except that the `group` and `user` properties default to the application-level +data if not specified. + +### `application_virtualenv` + +The `application_virtualenv` resource creates a Python virtualenv for the +deployment. + +```ruby +application '/srv/myapp' do + virtualenv +end +``` + +If no path property is given, the default is to create a `.env/` inside the +application deployment path. + +All actions and properties are the same as the [`python_virtualenv` resource](https://github.com/poise/poise-python#python_virtualenv). + +## Examples + +Some test recipes are available as examples for common application frameworks: + +* [Flask](https://github.com/poise/application_python/blob/master/test/cookbook/recipes/flask.rb) +* [Django](https://github.com/poise/application_python/blob/master/test/cookbook/recipes/django.rb) + +## Sponsors + +Development sponsored by [Chef Software](https://www.chef.io/), [Symonds & Son](http://symondsandson.com/), and [Orion](https://www.orionlabs.co/). + +The Poise test server infrastructure is sponsored by [Rackspace](https://rackspace.com/). + +## License + +Copyright 2015-2017, Noah Kantrowitz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..d2b12d0 --- /dev/null +++ b/Rakefile @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_boiler/rakefile' diff --git a/SUPPORTERS.md b/SUPPORTERS.md new file mode 100644 index 0000000..fa5a652 --- /dev/null +++ b/SUPPORTERS.md @@ -0,0 +1,81 @@ +# Supporters + +This cookbook wouldn't have been possible without the generous support of my +Kickstarter backers. Thanks so much to every one of you! + +* @kcunning +* Alberto Lorenzo Pulido +* Alex Gaynor +* Alexander Myasnikov +* Brooke Schreier Ganz +* Bryan McLellan +* Charles Johnson +* Chef Software, Inc. +* Chris Adams +* Christopher Petrilli +* David Thornton +* Derek Murawsky +* Fast Robot, LLC +* Fatty McAwesome Pants (Kate Heddleston) +* Greg Albrecht +* JD Harrington +* Jake Plimack +* Jason Cook +* Jeff Byrnes +* Jeff Forcier +* Jeff Lindsay +* Jennifer Davis +* John Fitch +* Jon Stacks +* Joshua SS Miller +* Julian Dunn +* Julien Phalip +* Kennon Kwok +* Kevin Duane +* Krzysztof Wilczynski +* Linda Goldstein +* Manny Toledo +* Marco A Morales +* Marcus Morris +* Mark Luntzel +* Martin B. Smith +* Mathieu Sauve-Frankel +* Matt Good +* Matt Juszczak +* Matt Ray +* Matt Stratton +* Michael Burns +* Miguel Landaeta +* Mike Schueler +* Nathan L Smith +* Nathen Harvey +* Paul Welch +* Peter Kropf +* Phil Mocek +* Practice Fusion Inc +* Ranjib +* Richard Jones +* Robert J. Berger +* Robin Levin +* Roland Moriz +* Ruben Orduz +* Russell Keith-Magee +* Ryan Hass +* Sam Clements +* Sean OMeara +* Seva Feldman +* Soo Choi +* Steven Danna +* Symonds & Son +* Todd Michael Bushnell +* Tracy Osborn +* Troy Ready +* Tyler Ball +* Vicky Tuite +* Ying Li +* Yvo van Doorn +* Zac Stevens +* lvh +* oolongtea +* smeuser +* tmonk42 diff --git a/chef/templates/celeryconfig.py.erb b/chef/templates/celeryconfig.py.erb new file mode 100644 index 0000000..3e47b9a --- /dev/null +++ b/chef/templates/celeryconfig.py.erb @@ -0,0 +1,5 @@ +# Generated by Chef for <%= @new_resource.to_s %> + +<%- @new_resource.options.each do |key, value| -%> +<%= key.upcase %> = <%= PoisePython::Utils.to_python(value) %> +<%- end -%> diff --git a/chef/templates/settings.py.erb b/chef/templates/settings.py.erb new file mode 100644 index 0000000..88ee90f --- /dev/null +++ b/chef/templates/settings.py.erb @@ -0,0 +1,13 @@ +# Generated by Chef for <%= @new_resource.to_s %> + +<%- unless @allowed_hosts.empty? -%> +ALLOWED_HOSTS = <%= PoisePython::Utils.to_python(@allowed_hosts) %> + +<%- end -%> +DEBUG = <%= PoisePython::Utils.to_python(@debug) %> + +DATABASES = <%= PoisePython::Utils.to_python(@databases) %> +<%- if @secret_key -%> + +SECRET_KEY = <%= PoisePython::Utils.to_python(@secret_key) %> +<%- end -%> diff --git a/examples/recipes-packaginator.rb b/examples/recipes-packaginator.rb deleted file mode 100644 index 7fa2bee..0000000 --- a/examples/recipes-packaginator.rb +++ /dev/null @@ -1,48 +0,0 @@ -application "packaginator" do - path "/srv/packaginator" - owner "nobody" - group "nogroup" - repository "https://github.com/coderanger/packaginator.git" - revision "master" - migrate true - packages ["libpq-dev", "git-core", "mercurial"] - - django do - packages ["redis"] - requirements "requirements/mkii.txt" - settings_template "settings.py.erb" - debug true - collectstatic "build_static --noinput" - database do - database "packaginator" - engine "postgresql_psycopg2" - username "packaginator" - password "awesome_password" - end - database_master_role "packaginator_database_master" - end - - gunicorn do - only_if { node['roles'].include? 'packaginator_application_server' } - app_module :django - port 8080 - end - - celery do - only_if { node['roles'].include? 'packaginator_application_server' } - config "celery_settings.py" - django true - celerybeat true - celerycam true - broker do - transport "redis" - end - end - - nginx_load_balancer do - only_if { node['roles'].include? 'packaginator_load_balancer' } - application_port 8080 - static_files "/site_media" => "site_media" - end - -end diff --git a/lib/poise_application_python.rb b/lib/poise_application_python.rb new file mode 100644 index 0000000..028595f --- /dev/null +++ b/lib/poise_application_python.rb @@ -0,0 +1,23 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +module PoiseApplicationPython + autoload :AppMixin, 'poise_application_python/app_mixin' + autoload :Error, 'poise_application_python/error' + autoload :Resources, 'poise_application_python/resources' + autoload :VERSION, 'poise_application_python/version' +end diff --git a/lib/poise_application_python/app_mixin.rb b/lib/poise_application_python/app_mixin.rb new file mode 100644 index 0000000..048ce6e --- /dev/null +++ b/lib/poise_application_python/app_mixin.rb @@ -0,0 +1,67 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise/backports' +require 'poise/utils' +require 'poise_application/app_mixin' +require 'poise_python/python_command_mixin' + + +module PoiseApplicationPython + # A helper mixin for Python application resources and providers. + # + # @since 4.0.0 + module AppMixin + include Poise::Utils::ResourceProviderMixin + + # A helper mixin for Python application resources. + module Resource + include PoiseApplication::AppMixin::Resource + include PoisePython::PythonCommandMixin::Resource + + # @!attribute parent_python + # Override the #parent_python from PythonCommandMixin to grok the + # application level parent as a default value. + # @return [PoisePython::Resources::PythonRuntime::Resource, nil] + parent_attribute(:python, type: :python_runtime, optional: true, default: lazy { app_state_python.equal?(self) ? nil : app_state_python }) + + # @attribute app_state_python + # The application-level Python parent. + # @return [PoisePython::Resources::PythonRuntime::Resource, nil] + def app_state_python(python=Poise::NOT_PASSED) + unless python == Poise::NOT_PASSED + app_state[:python] = python + end + app_state[:python] + end + + # A merged hash of environment variables for both the application state + # and parent python. + # + # @return [Hash] + def app_state_environment_python + env = app_state_environment + env = env.merge(parent_python.python_environment) if parent_python + env + end + end + + # A helper mixin for Python application providers. + module Provider + include PoiseApplication::AppMixin::Provider + end + end +end diff --git a/lib/poise_application_python/cheftie.rb b/lib/poise_application_python/cheftie.rb new file mode 100644 index 0000000..ad04a51 --- /dev/null +++ b/lib/poise_application_python/cheftie.rb @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_application_python/resources' diff --git a/lib/poise_application_python/error.rb b/lib/poise_application_python/error.rb new file mode 100644 index 0000000..dfdacd0 --- /dev/null +++ b/lib/poise_application_python/error.rb @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_application/error' + +module PoiseApplicationPython + # Base exception class for poise-application-python errors. + # + # @since 4.0.0 + class Error < PoiseApplication::Error + end +end diff --git a/lib/poise_application_python/resources.rb b/lib/poise_application_python/resources.rb new file mode 100644 index 0000000..a7b8fa3 --- /dev/null +++ b/lib/poise_application_python/resources.rb @@ -0,0 +1,26 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_application_python/resources/celery_beat' +require 'poise_application_python/resources/celery_config' +require 'poise_application_python/resources/celery_worker' +require 'poise_application_python/resources/django' +require 'poise_application_python/resources/gunicorn' +require 'poise_application_python/resources/pip_requirements' +require 'poise_application_python/resources/python' +require 'poise_application_python/resources/python_execute' +require 'poise_application_python/resources/python_package' +require 'poise_application_python/resources/virtualenv' diff --git a/lib/poise_application_python/resources/celery_beat.rb b/lib/poise_application_python/resources/celery_beat.rb new file mode 100644 index 0000000..e5664ca --- /dev/null +++ b/lib/poise_application_python/resources/celery_beat.rb @@ -0,0 +1,43 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_application_python/resources/celery_worker' + + +module PoiseApplicationPython + module Resources + # (see CeleryBeat::Resource) + # @since 4.0.0 + module CeleryBeat + class Resource < PoiseApplicationPython::Resources::CeleryWorker::Resource + provides(:application_celery_beat) + end + + class Provider < PoiseApplicationPython::Resources::CeleryWorker::Provider + provides(:application_celery_beat) + + private + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + resource.command("#{new_resource.python} -m celery --app=#{new_resource.app_module} beat") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/celery_config.rb b/lib/poise_application_python/resources/celery_config.rb new file mode 100644 index 0000000..f4a835e --- /dev/null +++ b/lib/poise_application_python/resources/celery_config.rb @@ -0,0 +1,109 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'uri' + +require 'chef/provider' +require 'chef/resource' +require 'poise' +require 'poise_application' +require 'poise_python' + +require 'poise_application_python/app_mixin' +require 'poise_application_python/error' + + +module PoiseApplicationPython + module Resources + # (see CeleryConfig::Resource) + # @since 4.0.0 + module CeleryConfig + # An `application_celery_config` resource to configure Celery workers. + # + # @since 4.0.0 + # @provides application_celery_config + # @action deploy + # @example + # application '/srv/myapp' do + # git '...' + # pip_requirements + # celery_config do + # options do + # broker_url '...' + # end + # end + # celeryd + # end + class Resource < Chef::Resource + include PoiseApplicationPython::AppMixin + provides(:application_celery_config) + actions(:deploy) + + attribute('', template: true, default_source: 'celeryconfig.py.erb') + # @!attribute group + # Owner for the Django application, defaults to application group. + # @return [String] + attribute(:group, kind_of: String, default: lazy { parent && parent.group }) + # @!attribute owner + # Owner for the Django application, defaults to application owner. + # @return [String] + attribute(:owner, kind_of: String, default: lazy { parent && parent.owner }) + attribute(:path, kind_of: String, default: lazy { default_path }) + + private + + def default_path + if ::File.directory?(name) + ::File.join(name, 'celeryconfig.py') + else + name + end + end + end + + # Provider for `application_celery_config`. + # + # @since 4.0.0 + # @see Resource + # @provides application_celery_config + class Provider < Chef::Provider + include PoiseApplicationPython::AppMixin + provides(:application_celery_config) + + # `deploy` action for `application_celery_config`. Writes config file. + # + # @return [void] + def action_deploy + notifying_block do + write_config + end + end + + private + + def write_config + file new_resource.path do + content new_resource.content + mode '640' + owner new_resource.owner + group new_resource.group + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/celery_worker.rb b/lib/poise_application_python/resources/celery_worker.rb new file mode 100644 index 0000000..520a446 --- /dev/null +++ b/lib/poise_application_python/resources/celery_worker.rb @@ -0,0 +1,78 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/provider' +require 'chef/resource' + +require 'poise_application_python/service_mixin' + + +module PoiseApplicationPython + module Resources + # (see CeleryWorker::Resource) + # @since 4.0.0 + module CeleryWorker + class Resource < Chef::Resource + include PoiseApplicationPython::ServiceMixin + provides(:application_celery_worker) + + attribute(:worker_pool, kind_of: [String, NilClass], default: "prefork") + attribute(:app_module, kind_of: [String, NilClass], default: lazy { default_app_module }) + + private + + # Compute the default application module to pass to gunicorn. This + # checks the app state and then looks for commonly used filenames. + # Raises an exception if no default can be found. + # + # @return [String] + def default_app_module + # If set in app_state, use that. + return app_state[:python_celery_module] if app_state[:python_celery_module] + # If a Django settings module is set, use everything by the last + # dotted component of it. to_s handles nil since that won't match. + return $1 if app_state_environment[:DJANGO_SETTINGS_MODULE].to_s =~ /^(.+?)\.[^.]+$/ + files = Dir.exist?(path) ? Dir.entries(path) : [] + # Try to find a known filename. + candidate_file = %w{tasks.py task.py celery.py main.py app.py application.py}.find {|file| files.include?(file) } + # Try the first Python file. Do I really want this? + candidate_file ||= files.find {|file| file.end_with?('.py') } + if candidate_file + ::File.basename(candidate_file, '.py') + else + nil + end + end + + end + + class Provider < Chef::Provider + include PoiseApplicationPython::ServiceMixin + provides(:application_celery_worker) + + private + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + raise PoiseApplicationPython::Error.new("Unable to determine app module for #{new_resource}") unless new_resource.app_module + resource.command("#{new_resource.python} -m celery --app=#{new_resource.app_module} -P #{new_resource.worker_pool} worker") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/django.rb b/lib/poise_application_python/resources/django.rb new file mode 100644 index 0000000..61dd5d7 --- /dev/null +++ b/lib/poise_application_python/resources/django.rb @@ -0,0 +1,355 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'uri' + +require 'chef/provider' +require 'chef/resource' +require 'poise' +require 'poise_application' +require 'poise_python' + +require 'poise_application_python/app_mixin' +require 'poise_application_python/error' + + +module PoiseApplicationPython + module Resources + # (see Django::Resource) + # @since 4.0.0 + module Django + # Aliases for Django database engine names. Based on https://github.com/kennethreitz/dj-database-url/blob/master/dj_database_url.py + # Copyright 2014-2017, Kenneth Reitz. + ENGINE_ALIASES = { + 'postgres' => 'django.db.backends.postgresql_psycopg2', + 'postgresql' => 'django.db.backends.postgresql_psycopg2', + 'pgsql' => 'django.db.backends.postgresql_psycopg2', + 'postgis' => 'django.contrib.gis.db.backends.postgis', + 'mysql2' => 'django.db.backends.mysql', + 'mysqlgis' => 'django.contrib.gis.db.backends.mysql', + 'spatialite' => 'django.contrib.gis.db.backends.spatialite', + 'sqlite' => 'django.db.backends.sqlite3', + } + + # An `application_django` resource to configure Django applications. + # + # @since 4.0.0 + # @provides application_django + # @action deploy + # @example + # application '/srv/myapp' do + # git '...' + # pip_requirements + # django do + # database do + # host node['db_host'] + # end + # end + # gunicorn do + # port 8080 + # end + # end + class Resource < Chef::Resource + include PoiseApplicationPython::AppMixin + provides(:application_django) + actions(:deploy) + + # @!attribute allowed_hosts + # Value for `ALLOWED_HOSTS` in the Django settings. + # @return [String, Array] + attribute(:allowed_hosts, kind_of: [String, Array], default: lazy { [] }) + # @!attribute collectstatic + # Set to false to disable running manage.py collectstatic during + # deployment. + # @todo This could auto-detect based on config vars in settings? + # @return [Boolean] + attribute(:collectstatic, equal_to: [true, false], default: true) + # @!attribute database + # Option collector attribute for Django database configuration. + # @return [Hash] + # @example Setting via block + # database do + # engine 'postgresql' + # database 'blog' + # end + # @example Setting via URL + # database 'postgresql://localhost/blog' + attribute(:database, option_collector: true, parser: :parse_database_url, forced_keys: %i{name}) + # @!attribute debug + # Enable debug mode for Django. + # @note + # If you use this in production you will get everything you deserve. + # @return [Boolean] + attribute(:debug, equal_to: [true, false], default: false) + # @!attribute group + # Owner for the Django application, defaults to application group. + # @return [String] + attribute(:group, kind_of: String, default: lazy { parent && parent.group }) + # @!attribute local_settings + # Template content attribute for the contents of local_settings.py. + # @todo Redo this doc to cover the actual attributes created. + # @return [Poise::Helpers::TemplateContent] + attribute(:local_settings, template: true, default_source: 'settings.py.erb', default_options: lazy { default_local_settings_options }) + # @!attribute local_settings_path + # Path to write local settings to. If given as a relative path, + # will be expanded against {#path}. Set to false to disable writing + # local settings. Defaults to local_settings.py next to + # {#setting_module}. + # @return [String, nil false] + attribute(:local_settings_path, kind_of: [String, NilClass, FalseClass], default: lazy { default_local_settings_path }) + # @!attribute migrate + # Run database migrations. This is a bad idea for real apps. Please + # do not use it. + # @return [Boolean] + attribute(:migrate, equal_to: [true, false], default: false) + # @!attribute manage_path + # Path to manage.py. Defaults to scanning for the nearest manage.py + # to {#path}. + # @return [String] + attribute(:manage_path, kind_of: String, default: lazy { default_manage_path }) + # @!attribute owner + # Owner for the Django application, defaults to application owner. + # @return [String] + attribute(:owner, kind_of: String, default: lazy { parent && parent.owner }) + # @!attribute secret_key + # Value for `SECRET_KEY` in the Django settings. If unset, no key is + # added to the local settings. + # @return [String, false] + attribute(:secret_key, kind_of: [String, FalseClass]) + # @!attribute settings_module + # Django settings module in dotted notation. Set to false to disable + # anything related to settings. Defaults to scanning for the nearest + # settings.py to {#path}. + # @return [Boolean] + attribute(:settings_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_settings_module }) + # @!attribute syncdb + # Run database sync. This is a bad idea for real apps. Please do not + # use it. + # @return [Boolean] + attribute(:syncdb, equal_to: [true, false], default: false) + # @!attribute wsgi_module + # WSGI application module in dotted notation. Set to false to disable + # anything related to WSGI. Defaults to scanning for the nearest + # wsgi.py to {#path}. + # @return [Boolean] + attribute(:wsgi_module, kind_of: [String, NilClass, FalseClass], default: lazy { default_wsgi_module }) + + private + + # Default value for {#local_settings_options}. Adds Django settings data + # from the resource to be rendered in the local settings template. + # + # @return [Hash] + def default_local_settings_options + {}.tap do |options| + options[:allowed_hosts] = Array(allowed_hosts) + options[:databases] = {} + options[:databases]['default'] = database.inject({}) do |memo, (key, value)| + key = key.to_s.upcase + # Deal with engine aliases here too, just in case. + value = resolve_engine(value) if key == 'ENGINE' + memo[key] = value + memo + end + options[:debug] = debug + options[:secret_key] = secret_key + end + end + + # Default value for {#local_settings_path}, local_settings.py next to + # the configured {#settings_module}. + # + # @return [String, nil] + def default_local_settings_path + # If no settings module, no default local settings. + return unless settings_module + settings_path = PoisePython::Utils.module_to_path(settings_module, path) + ::File.expand_path(::File.join('..', 'local_settings.py'), settings_path) + end + + # Default value for {#manage_path}, searches for manage.py in the + # application path. + # + # @return [String, nil] + def default_manage_path + find_file('manage.py') + end + + # Default value for {#settings_module}, searches for settings.py in the + # application path. + # + # @return [String, nil] + def default_settings_module + settings_path = find_file('settings.py') + if settings_path + PoisePython::Utils.path_to_module(settings_path, path) + else + nil + end + end + + # Default value for {#wsgi_module}, searchs for wsgi.py in the + # application path. + # + # @return [String, nil] + def default_wsgi_module + wsgi_path = find_file('wsgi.py') + if wsgi_path + PoisePython::Utils.path_to_module(wsgi_path, path) + else + nil + end + end + + # Format a URL for DATABASES. + # + # @return [Hash] + def parse_database_url(url) + parsed = URI(url) + {}.tap do |db| + # Store this for use later in #set_state, and maybe future use by + # Django in some magic world where operability happens. + db[:URL] = url + db[:ENGINE] = resolve_engine(parsed.scheme) + # Strip the leading /. + path = parsed.path ? parsed.path[1..-1] : parsed.path + # If we are using SQLite, make it an absolute path. + path = ::File.expand_path(path, self.path) if db[:ENGINE].include?('sqlite') + db[:NAME] = path if path && !path.empty? + db[:USER] = parsed.user if parsed.user && !parsed.user.empty? + db[:PASSWORD] = parsed.password if parsed.password && !parsed.password.empty? + db[:HOST] = parsed.host if parsed.host && !parsed.host.empty? + db[:PORT] = parsed.port if parsed.port && !parsed.port.nil? + end + end + + # Search for a file somewhere under the application path. Prefers files + # closer to the root, then sort alphabetically for stability. + # + # @param name [String] Filename to search for. + # @return [String, nil] + def find_file(name) + num_separators = lambda do |path| + if ::File::ALT_SEPARATOR && path.include?(::File::ALT_SEPARATOR) + # :nocov: + path.count(::File::ALT_SEPARATOR) + # :nocov: + else + path.count(::File::SEPARATOR) + end + end + Dir[::File.join(path, '**', name)].min do |a, b| + cmp = num_separators.call(a) <=> num_separators.call(b) + if cmp == 0 + cmp = a <=> b + end + cmp + end + end + + # Resolve Django database engine from shortname to dotted module. + # + # @param name [String, nil] Engine name. + # @return [String, nil] + def resolve_engine(name) + if name && !name.empty? && !name.include?('.') + ENGINE_ALIASES[name] || "django.db.backends.#{name}" + else + name + end + end + + end + + # Provider for `application_django`. + # + # @since 4.0.0 + # @see Resource + # @provides application_django + class Provider < Chef::Provider + include PoiseApplicationPython::AppMixin + provides(:application_django) + + # `deploy` action for `application_django`. Ensure all configuration + # files are created and other deploy tasks resolved. + # + # @return [void] + def action_deploy + set_state + notifying_block do + write_config + run_syncdb + run_migrate + run_collectstatic + end + end + + private + + # Set app_state variables for future services et al. + def set_state + # Set environment variables for later services. + new_resource.app_state_environment[:DJANGO_SETTINGS_MODULE] = new_resource.settings_module if new_resource.settings_module + new_resource.app_state_environment[:DATABASE_URL] = new_resource.database[:URL] if new_resource.database[:URL] + # Set the app module. + new_resource.app_state[:python_wsgi_module] = new_resource.wsgi_module if new_resource.wsgi_module + end + + # Create the database using the older syncdb command. + def run_syncdb + manage_py_execute('syncdb', '--noinput') if new_resource.syncdb + end + + # Create the database using the newer migrate command. This should work + # for either South or the built-in migrations support. + def run_migrate + manage_py_execute('migrate', '--noinput') if new_resource.migrate + end + + # Run the asset pipeline. + def run_collectstatic + manage_py_execute('collectstatic', '--noinput') if new_resource.collectstatic + end + + # Create the local config settings. + def write_config + # Allow disabling the local settings. + return unless new_resource.local_settings_path + file new_resource.local_settings_path do + content new_resource.local_settings_content + mode '640' + owner new_resource.owner + group new_resource.group + end + end + + # Run a manage.py command using `python_execute`. + def manage_py_execute(*cmd) + raise PoiseApplicationPython::Error.new("Unable to find a find a manage.py for #{new_resource}, please set manage_path") unless new_resource.manage_path + python_execute "manage.py #{cmd.join(' ')}" do + python_from_parent new_resource + command [::File.expand_path(new_resource.manage_path, new_resource.path)] + cmd + cwd new_resource.path + environment new_resource.app_state_environment + group new_resource.group + user new_resource.owner + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/gunicorn.rb b/lib/poise_application_python/resources/gunicorn.rb new file mode 100644 index 0000000..e9daed3 --- /dev/null +++ b/lib/poise_application_python/resources/gunicorn.rb @@ -0,0 +1,127 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'shellwords' + +require 'chef/provider' +require 'chef/resource' +require 'poise' + +require 'poise_application_python/service_mixin' + + +module PoiseApplicationPython + module Resources + # (see Gunicorn::Resource) + # @since 4.0.0 + module Gunicorn + class Resource < Chef::Resource + include PoiseApplicationPython::ServiceMixin + provides(:application_gunicorn) + + attribute(:app_module, kind_of: [String, NilClass], default: lazy { default_app_module }) + attribute(:bind, kind_of: [String, Array], default: '0.0.0.0:80') + attribute(:config, kind_of: [String, NilClass]) + attribute(:preload_app, equal_to: [true, false], default: false) + attribute(:version, kind_of: [String, TrueClass, FalseClass], default: true) + + # Helper to set {#bind} with just a port number. + # + # @param val [String, Integer] Port number to use. + # @return [void] + def port(val) + bind("0.0.0.0:#{val}") + end + + private + + # Compute the default application module to pass to gunicorn. This + # checks the app state and then looks for commonly used filenames. + # Returns nil if no default can be found. + # + # @return [String] + def default_app_module + # If set in app_state, use that. + return app_state[:python_wsgi_module] if app_state[:python_wsgi_module] + files = Dir.exist?(path) ? Dir.entries(path) : [] + # Try to find a known filename. + candidate_file = %w{wsgi.py main.py app.py application.py}.find {|file| files.include?(file) } + # Try the first Python file. Do I really want this? + candidate_file ||= files.find {|file| file.end_with?('.py') } + if candidate_file + ::File.basename(candidate_file, '.py') + else + nil + end + end + + end + + class Provider < Chef::Provider + include PoiseApplicationPython::ServiceMixin + provides(:application_gunicorn) + + def action_enable + notifying_block do + install_gunicorn + end + super + end + + private + + def install_gunicorn + return unless new_resource.version + python_package 'gunicorn' do + python_from_parent new_resource + version new_resource.version if new_resource.version.is_a?(String) + end + end + + def gunicorn_command_options + # Based on http://docs.gunicorn.org/en/latest/settings.html + [].tap do |cmd| + # What options are common enough to deal with here? + # %w{config backlog workers worker_class threads worker_connections timeout graceful_timeout keepalive}.each do |opt| + %w{config}.each do |opt| + val = new_resource.send(opt) + if val && !(val.respond_to?(:empty?) && val.empty?) + cmd_opt = opt.gsub(/_/, '-') + cmd << "--#{cmd_opt} #{Shellwords.escape(val)}" + end + end + # Can be given multiple times. + Array(new_resource.bind).each do |bind| + cmd << "--bind #{bind}" if bind + end + # --preload doesn't take an argument and the name doesn't match. + if new_resource.preload_app + cmd << '--preload' + end + end + end + + # (see PoiseApplication::ServiceMixin#service_options) + def service_options(resource) + super + raise PoiseApplicationPython::Error.new("Unable to determine app module for #{new_resource}") unless new_resource.app_module + resource.command("#{new_resource.python} -m gunicorn.app.wsgiapp #{gunicorn_command_options.join(' ')} #{new_resource.app_module}") + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/pip_requirements.rb b/lib/poise_application_python/resources/pip_requirements.rb new file mode 100644 index 0000000..8c236d8 --- /dev/null +++ b/lib/poise_application_python/resources/pip_requirements.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_python/resources/pip_requirements' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PipRequirements::Resource) + # @since 4.0.0 + module PipRequirements + # An `application_pip_requirements` resource to manage Python virtual + # environments inside an Application cookbook deployment. + # + # @provides application_pip_requirements + # @provides application_virtualenv + # @action install + # @action upgrade + # @example + # application '/app' do + # pip_requirements + # end + class Resource < PoisePython::Resources::PipRequirements::Resource + include PoiseApplicationPython::AppMixin + provides(:application_pip_requirements) + subclass_providers! + + # @todo This should handle relative paths against parent.path. + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.owner }) + end + end + end +end diff --git a/lib/poise_application_python/resources/python.rb b/lib/poise_application_python/resources/python.rb new file mode 100644 index 0000000..021bec0 --- /dev/null +++ b/lib/poise_application_python/resources/python.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_python/resources/python_runtime' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see Python::Resource) + # @since 4.0.0 + module Python + # An `application_python` resource to manage Python runtimes + # inside an Application cookbook deployment. + # + # @provides application_python + # @provides application_python_runtime + # @action install + # @action uninstall + # @example + # application '/app' do + # python '2' + # end + class Resource < PoisePython::Resources::PythonRuntime::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python) + provides(:application_python_runtime) + container_default(false) + subclass_providers! + + # Set this resource as the app_state's parent python. + # + # @api private + def after_created + super.tap do |val| + app_state_python(self) + end + end + + end + end + end +end diff --git a/lib/poise_application_python/resources/python_execute.rb b/lib/poise_application_python/resources/python_execute.rb new file mode 100644 index 0000000..6b79fdf --- /dev/null +++ b/lib/poise_application_python/resources/python_execute.rb @@ -0,0 +1,89 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_python/resources/python_execute' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PythonExecute::Resource) + # @since 4.0.0 + module PythonExecute + # An `application_python_execute` resource to run Python commands inside an + # Application cookbook deployment. + # + # @provides application_python_execute + # @action run + # @example + # application '/srv/myapp' do + # python_execute 'setup.py install' + # end + class Resource < PoisePython::Resources::PythonExecute::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python_execute) + + def initialize(*args) + super + # Clear some instance variables so my defaults work. + remove_instance_variable(:@cwd) if defined?(@cwd) + remove_instance_variable(:@group) if defined?(@group) + remove_instance_variable(:@user) if defined?(@user) + end + + # #!attribute cwd + # Override the default directory to be the app path if unspecified. + # @return [String] + attribute(:cwd, kind_of: [String, NilClass, FalseClass], default: lazy { parent && parent.path }) + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass, FalseClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass, FalseClass], default: lazy { parent && parent.owner }) + end + + # The default provider for `application_python_execute`. + # + # @see Resource + # @provides application_python_execute + class Provider < PoisePython::Resources::PythonExecute::Provider + provides(:application_python_execute) + + private + + # Override environment to add the application envivonrment instead. + # + # @return [Hash] + def environment + super.tap do |environment| + # Don't use the app_state_environment_python because we already have + # those values in place. + environment.update(new_resource.app_state_environment) + # Re-apply the resource environment for correct ordering. + environment.update(new_resource.environment) if new_resource.environment + end + end + end + + end + end +end diff --git a/lib/poise_application_python/resources/python_package.rb b/lib/poise_application_python/resources/python_package.rb new file mode 100644 index 0000000..54ec9ee --- /dev/null +++ b/lib/poise_application_python/resources/python_package.rb @@ -0,0 +1,62 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_python/resources/python_package' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see PythonPackage::Resource) + # @since 4.0.0 + module PythonPackage + # An `application_python_package` resource to install Python + # packages inside an Application cookbook deployment. + # + # @provides application_python_package + # @action install + # @action upgrade + # @action remove + # @example + # application '/srv/myapp' do + # python_package 'requests' + # end + class Resource < PoisePython::Resources::PythonPackage::Resource + include PoiseApplicationPython::AppMixin + provides(:application_python_package) + subclass_providers! + + def initialize(*args) + super + # For older Chef. + @resource_name = :application_python_package + end + + # #!attribute group + # Override the default group to be the app group if unspecified. + # @return [String, Integer] + attribute(:group, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.group }) + + # #!attribute user + # Override the default user to be the app owner if unspecified. + # @return [String, Integer] + attribute(:user, kind_of: [String, Integer, NilClass], default: lazy { parent && parent.owner }) + end + + end + end +end diff --git a/lib/poise_application_python/resources/virtualenv.rb b/lib/poise_application_python/resources/virtualenv.rb new file mode 100644 index 0000000..3bec6fa --- /dev/null +++ b/lib/poise_application_python/resources/virtualenv.rb @@ -0,0 +1,75 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_python/resources/python_virtualenv' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + module Resources + # (see Virtualenv::Resource) + # @since 4.0.0 + module Virtualenv + # An `application_virtualenv` resource to manage Python virtual + # environments inside an Application cookbook deployment. + # + # @provides application_virtualenv + # @provides application_python_virtualenv + # @action create + # @action delete + # @example + # application '/app' do + # virtualenv + # end + class Resource < PoisePython::Resources::PythonVirtualenv::Resource + include PoiseApplicationPython::AppMixin + provides(:application_virtualenv) + provides(:application_python_virtualenv) + container_default(false) + subclass_providers! + + # @!attribute path + # Override the normal path property to use name/.virtualenv for better + # compatibility with the application resource DSL. + # @return [String] + attribute(:path, kind_of: String, default: lazy { default_path }) + + # Set this resource as the app_state's parent python. + # + # @api private + def after_created + super.tap do |val| + # Force evaluation so we get any current parent if set. + parent_python + app_state_python(self) + end + end + + private + + # Default value for the {#path} property. + # + # @return [String] + def default_path + # @todo This should handle relative paths as a name. + ::File.join(name, '.virtualenv') + end + + end + end + end +end diff --git a/lib/poise_application_python/service_mixin.rb b/lib/poise_application_python/service_mixin.rb new file mode 100644 index 0000000..2c52420 --- /dev/null +++ b/lib/poise_application_python/service_mixin.rb @@ -0,0 +1,57 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise/utils' +require 'poise_application/service_mixin' +require 'poise_languages/utils' + +require 'poise_application_python/app_mixin' + + +module PoiseApplicationPython + # A helper mixin for Python service resources and providers. + # + # @since 4.0.0 + module ServiceMixin + include Poise::Utils::ResourceProviderMixin + + # A helper mixin for Python service resources. + module Resource + include PoiseApplication::ServiceMixin::Resource + include PoiseApplicationPython::AppMixin::Resource + end + + # A helper mixin for Python service providers. + module Provider + include PoiseApplication::ServiceMixin::Provider + include PoiseApplicationPython::AppMixin::Provider + + # Set up the service for running Python stuff. + def service_options(resource) + super + # Closure scoping for #python_command below. + self_ = self + # Create a new singleton method that fills in Python for you. + resource.define_singleton_method(:python_command) do |val| + resource.command("#{self_.new_resource.python} #{PoiseLanguages::Utils.absolute_command(val, path: self_.new_resource.app_state_environment_python['PATH'])}") + end + # Include env vars as needed. + resource.environment.update(new_resource.parent_python.python_environment) if new_resource.parent_python + end + + end + end +end diff --git a/lib/poise_application_python/version.rb b/lib/poise_application_python/version.rb new file mode 100644 index 0000000..50af440 --- /dev/null +++ b/lib/poise_application_python/version.rb @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module PoiseApplicationPython + VERSION = '4.0.1.pre' +end diff --git a/metadata.rb b/metadata.rb deleted file mode 100644 index a06b064..0000000 --- a/metadata.rb +++ /dev/null @@ -1,13 +0,0 @@ -name "application_python" -maintainer "Opscode, Inc." -maintainer_email "cookbooks@opscode.com" -license "Apache 2.0" -description "Deploys and configures Python-based applications" -long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version "3.0.1" - -%w{ python gunicorn supervisor }.each do |cb| - depends cb -end - -depends "application", "~> 3.0" diff --git a/poise-application-python.gemspec b/poise-application-python.gemspec new file mode 100644 index 0000000..8394013 --- /dev/null +++ b/poise-application-python.gemspec @@ -0,0 +1,48 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'poise_application_python/version' + +Gem::Specification.new do |spec| + spec.name = 'poise-application-python' + spec.version = PoiseApplicationPython::VERSION + spec.authors = ['Noah Kantrowitz'] + spec.email = %w{noah@coderanger.net} + spec.description = "A Chef cookbook for deploying Python application code." + spec.summary = spec.description + spec.homepage = 'https://github.com/poise/application_python' + spec.license = 'Apache-2.0' + spec.metadata['halite_name'] = 'application_python' + spec.metadata['platforms'] = 'any' + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = %w{lib} + + spec.add_dependency 'chef', '>= 12.1', '< 14' + spec.add_dependency 'halite', '~> 1.0' + spec.add_dependency 'poise', '~> 2.0' + spec.add_dependency 'poise-application', '~> 5.0' + spec.add_dependency 'poise-python', '~> 1.0' + spec.add_dependency 'poise-service', '~> 1.0' + + spec.add_development_dependency 'poise-boiler', '~> 1.6' + spec.add_development_dependency 'poise-application-git', '~> 1.2' + spec.add_development_dependency 'poise-build-essential', '~> 1.0' +end diff --git a/providers/celery.rb b/providers/celery.rb deleted file mode 100644 index 30fe27f..0000000 --- a/providers/celery.rb +++ /dev/null @@ -1,123 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: django -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe "supervisor" - - raise "You must specify an application module to load" unless new_resource.config - - if !new_resource.restart_command - r = new_resource - new_resource.restart_command do - run_context.resource_collection.find(:supervisor_service => "#{r.application.name}-celeryd").run_action(:restart) if r.celeryd - run_context.resource_collection.find(:supervisor_service => "#{r.application.name}-celerybeat").run_action(:restart) if r.celerybeat - run_context.resource_collection.find(:supervisor_service => "#{r.application.name}-celerycam").run_action(:restart) if r.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 0a58936..0000000 --- a/providers/django.rb +++ /dev/null @@ -1,146 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: django -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'tmpdir' - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe 'python' - - new_resource.migration_command "#{::File.join(new_resource.virtualenv, "bin", "python")} manage.py syncdb --noinput" if !new_resource.migration_command - - new_resource.symlink_before_migrate.update({ - new_resource.local_settings_base => new_resource.local_settings_file, - }) -end - -action :before_deploy do - - install_packages - - created_settings_file - -end - -action :before_migrate do - - if new_resource.requirements.nil? - # look for requirements.txt files in common locations - [ - ::File.join(new_resource.release_path, "requirements", "#{node.chef_environment}.txt"), - ::File.join(new_resource.release_path, "requirements.txt") - ].each do |path| - if ::File.exists?(path) - new_resource.requirements path - break - end - end - end - if new_resource.requirements - Chef::Log.info("Installing using requirements file: #{new_resource.requirements}") - pip_cmd = ::File.join(new_resource.virtualenv, 'bin', 'pip') - execute "#{pip_cmd} install --source=#{Dir.tmpdir} -r #{new_resource.requirements}" do - cwd new_resource.release_path - # seems that if we don't set the HOME env var pip tries to log to /root/.pip, which fails due to permissions - # setting HOME also enables us to control pip behavior on per-project basis by dropping off a pip.conf file there - # GIT_SSH allow us to reuse the deployment key used to clone the main - # repository to clone any private requirements - if new_resource.deploy_key - environment 'HOME' => ::File.join(new_resource.path,'shared'), 'GIT_SSH' => "#{new_resource.path}/deploy-ssh-wrapper" - else - environment 'HOME' => ::File.join(new_resource.path,'shared') - end - user new_resource.owner - group new_resource.group - end - else - Chef::Log.debug("No requirements file found") - end - -end - -action :before_symlink do - - if new_resource.collectstatic - cmd = new_resource.collectstatic.is_a?(String) ? new_resource.collectstatic : "collectstatic --noinput" - execute "#{::File.join(new_resource.virtualenv, "bin", "python")} manage.py #{cmd}" do - user new_resource.owner - group new_resource.group - cwd new_resource.release_path - end - end - - ruby_block "remove_run_migrations" do - block do - if node.role?("#{new_resource.application.name}_run_migrations") - Chef::Log.info("Migrations were run, removing role[#{new_resource.name}_run_migrations]") - node.run_list.remove("role[#{new_resource.name}_run_migrations]") - end - end - end - -end - -action :before_restart do -end - -action :after_restart do -end - -protected - -def install_packages - python_virtualenv new_resource.virtualenv do - path new_resource.virtualenv - owner new_resource.owner - group new_resource.group - action :create - end - - new_resource.packages.each do |name, ver| - python_pip name do - version ver if ver && ver.length > 0 - virtualenv new_resource.virtualenv - user new_resource.owner - group new_resource.group - action :install - end - end -end - -def created_settings_file - host = new_resource.find_database_server(new_resource.database_master_role) - - template "#{new_resource.path}/shared/#{new_resource.local_settings_base}" do - source new_resource.settings_template || "settings.py.erb" - cookbook new_resource.settings_template ? new_resource.cookbook_name.to_s : "application_python" - owner new_resource.owner - group new_resource.group - mode "644" - variables new_resource.settings.clone - variables.update :django => new_resource, :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 4a863b2..0000000 --- a/providers/gunicorn.rb +++ /dev/null @@ -1,152 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Provider:: gunicorn -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'tmpdir' - -include Chef::DSL::IncludeRecipe - -action :before_compile do - - include_recipe "supervisor" - - install_packages - - django_resource = new_resource.application.sub_resources.select{|res| res.type == :django}.first - gunicorn_install "gunicorn-#{new_resource.application.name}" do - virtualenv django_resource ? django_resource.virtualenv : new_resource.virtualenv - end - - if !new_resource.restart_command - r = new_resource - new_resource.restart_command do - run_context.resource_collection.find(:supervisor_service => r.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' - if new_resource.socket_path - listen_uri = "unix:#{new_resource.socket_path}" - else - listen_uri = "#{new_resource.host}:#{new_resource.port}" - end - listen listen_uri - backlog new_resource.backlog - worker_processes new_resource.workers - worker_class new_resource.worker_class.to_s - #worker_connections - worker_max_requests new_resource.max_requests - worker_timeout new_resource.timeout - worker_keepalive new_resource.keepalive - #debug - #trace - preload_app new_resource.preload_app - #daemon - pid new_resource.pidfile - #umask - #logfile - #loglevel - #proc_name - end - - supervisor_service new_resource.application.name do - action :enable - if new_resource.environment - environment new_resource.environment - end - if new_resource.app_module == :django - django_resource = new_resource.application.sub_resources.select{|res| res.type == :django}.first - raise "No Django deployment resource found" unless django_resource - base_command = "#{::File.join(django_resource.virtualenv, "bin", "python")} manage.py run_gunicorn" - else - gunicorn_command = new_resource.virtualenv.nil? ? "gunicorn" : "#{::File.join(new_resource.virtualenv, "bin", "gunicorn")}" - base_command = "#{gunicorn_command} #{new_resource.app_module}" - end - command "#{base_command} -c #{new_resource.application.path}/shared/gunicorn_config.py" - directory new_resource.directory.nil? ? ::File.join(new_resource.path, "current") : new_resource.directory - autostart new_resource.autostart - user new_resource.owner - end - -end - -action :before_migrate do - install_requirements -end - -action :before_symlink do -end - -action :before_restart do -end - -action :after_restart do -end - -protected - -def install_packages - new_resource.packages.each do |name, ver| - python_pip name do - version ver if ver && ver.length > 0 - virtualenv new_resource.virtualenv - action :install - end - end -end - -def install_requirements - if new_resource.requirements.nil? - # look for requirements.txt files in common locations - [ - ::File.join(new_resource.release_path, "requirements", "#{node.chef_environment}.txt"), - ::File.join(new_resource.release_path, "requirements.txt") - ].each do |path| - if ::File.exists?(path) - new_resource.requirements path - break - end - end - end - if new_resource.requirements - Chef::Log.info("Installing using requirements file: #{new_resource.requirements}") - # TODO normalise with python/providers/pip.rb 's pip_cmd - if new_resource.virtualenv.nil? - pip_cmd = 'pip' - else - pip_cmd = ::File.join(new_resource.virtualenv, 'bin', 'pip') - end - execute "#{pip_cmd} install --src=#{Dir.tmpdir} -r #{new_resource.requirements}" do - cwd new_resource.release_path - end - else - Chef::Log.debug("No requirements file found") - end -end diff --git a/resources/celery.rb b/resources/celery.rb deleted file mode 100644 index 205aac0..0000000 --- a/resources/celery.rb +++ /dev/null @@ -1,48 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: celery -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -include ApplicationCookbook::ResourceBase - -attribute :config, :kind_of => [String, NilClass], :default => nil -attribute :template, :kind_of => [String, NilClass], :default => nil -attribute :django, :kind_of => [TrueClass, FalseClass], :default => false -attribute :celeryd, :kind_of => [TrueClass, FalseClass], :default => true -attribute :celerybeat, :kind_of => [TrueClass, FalseClass], :default => false -attribute :celerycam, :kind_of => [TrueClass, FalseClass], :default => false -attribute :camera_class, :kind_of => [String, NilClass], :default => nil -attribute :enable_events, :kind_of => [TrueClass, FalseClass], :default => false -attribute :environment, :kind_of => [Hash], :default => {} -attribute :queues, :kind_of => [Array,NilClass], :default => nil - -def config_base - config.split(/[\\\/]/).last -end - -def broker(*args, &block) - @broker ||= Mash.new - @broker.update(options_block(*args, &block)) - @broker -end - -def results(*args, &block) - @results ||= Mash.new - @results.update(options_block(*args, &block)) - @results -end diff --git a/resources/django.rb b/resources/django.rb deleted file mode 100644 index 5c5e601..0000000 --- a/resources/django.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: django -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -include ApplicationCookbook::ResourceBase - -attribute :database_master_role, :kind_of => [String, NilClass], :default => nil -attribute :packages, :kind_of => [Array, Hash], :default => [] -attribute :requirements, :kind_of => [NilClass, String, FalseClass], :default => nil -attribute :legacy_database_settings, :kind_of => [TrueClass, FalseClass], :default => false -attribute :settings, :kind_of => Hash, :default => {} -# Actually defaults to "settings.py.erb", but nil means it wasn't set by the user -attribute :settings_template, :kind_of => [String, NilClass], :default => nil -attribute :local_settings_file, :kind_of => String, :default => 'local_settings.py' -attribute :debug, :kind_of => [TrueClass, FalseClass], :default => false -attribute :collectstatic, :kind_of => [TrueClass, FalseClass, String], :default => false - -def local_settings_base - local_settings_file.split(/[\\\/]/).last -end - -def virtualenv - "#{path}/shared/env" -end - -def database(*args, &block) - @database ||= Mash.new - @database.update(options_block(*args, &block)) - @database -end diff --git a/resources/gunicorn.rb b/resources/gunicorn.rb deleted file mode 100644 index 30e51ca..0000000 --- a/resources/gunicorn.rb +++ /dev/null @@ -1,50 +0,0 @@ -# -# Author:: Noah Kantrowitz -# Cookbook Name:: application_python -# Resource:: gunicorn -# -# Copyright:: 2011, Opscode, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -include ApplicationCookbook::ResourceBase - -attribute :app_module, :kind_of => [String, Symbol, NilClass], :default => nil -# Actually defaults to "settings.py.erb", but nil means it wasn't set by the user -attribute :settings_template, :kind_of => [String, NilClass], :default => nil -attribute :socket_path, :kind_of => [String, NilClass], :default => nil -attribute :host, :kind_of => String, :default => '0.0.0.0' -attribute :port, :kind_of => Integer, :default => 8080 -attribute :backlog, :kind_of => Integer, :default => 2048 -attribute :workers, :kind_of => Integer, :default => (node['cpu'] && node['cpu']['total']) && [node['cpu']['total'].to_i * 4, 8].min || 8 -attribute :worker_class, :kind_of => [String, Symbol], :default => :sync -attribute :worker_connections, :kind_of => Integer, :default => 1000 -attribute :max_requests, :kind_of => Integer, :default => 0 -attribute :timeout, :kind_of => Integer, :default => 30 -attribute :keepalive, :kind_of => Integer, :default => 2 -attribute :debug, :kind_of => [TrueClass, FalseClass], :default => false -attribute :trace, :kind_of => [TrueClass, FalseClass], :default => false -attribute :preload_app, :kind_of => [TrueClass, FalseClass], :default => false -attribute :daemon, :kind_of => [TrueClass, FalseClass], :default => false -attribute :pidfile, :kind_of => [String, NilClass], :default => nil -attribute :umask, :kind_of => [String, Integer], :default => 0 -attribute :logfile, :kind_of => String, :default => '-' -attribute :loglevel, :kind_of => [String, Symbol], :default => :info -attribute :proc_name, :kind_of => [String, NilClass], :default => nil -attribute :virtualenv, :kind_of => String, :default => nil -attribute :packages, :kind_of => [Array, Hash], :default => [] -attribute :requirements, :kind_of => [NilClass, String, FalseClass], :default => nil -attribute :environment, :kind_of => [Hash], :default => {} -attribute :autostart, :kind_of => [TrueClass, FalseClass], :default => false -attribute :directory, :kind_of => [NilClass, String], :default => nil diff --git a/templates/default/celeryconfig.py.erb b/templates/default/celeryconfig.py.erb deleted file mode 100644 index 1aad650..0000000 --- a/templates/default/celeryconfig.py.erb +++ /dev/null @@ -1,15 +0,0 @@ -BROKER_TRANSPORT = "<%= @broker[:transport] %>" -BROKER_HOST = "<%= @broker[:host] %>" -<% %w{port user password vhost}.each do |key| %> -<% if @broker[key] %> -BROKER_<%= key.upcase %> = "<%= @broker[key] %>" -<% end %> -<% end %> -<% %w{pool_limit connection_timeout connection_retry connection_max_retries}.each do |key| %> -<% if @broker[key] %> -BROKER_<%= key.upcase %> = <%= @broker[key] %> -<% end %> -<% end %> -<% if @broker[:use_ssl] %> -BROKER_USE_SSL = True -<% end %> diff --git a/templates/default/settings.py.erb b/templates/default/settings.py.erb deleted file mode 100644 index 794eb25..0000000 --- a/templates/default/settings.py.erb +++ /dev/null @@ -1,20 +0,0 @@ -DEBUG = <%= @debug ? "True" : "False" %> - -DATABASES = { - 'default': { - 'NAME': '<%= @database[:settings][:database] %>', - 'ENGINE': 'django.db.backends.<%= @database[:settings][:adapter] %>', - 'USER': '<%= @database[:settings][:username] %>', - 'PASSWORD': '<%= @database[:settings][:password] %>', - 'HOST': '<%= @database[:host] %>', - 'PORT': '<%= @database[:settings][:port] %>', - }, -} -<% if @database[:legacy] -%> -DATABASE_ENGINE = DATABASES['default']['ENGINE'] -DATABASE_NAME = DATABASES['default']['NAME'] -DATABASE_USER = DATABASES['default']['USER'] -DATABASE_PASSWORD = DATABASES['default']['PASSWORD'] -DATABASE_HOST = DATABASES['default']['HOST'] -DATABASE_PORT = DATABASES['default']['PORT'] -<% end -%> diff --git a/test/cookbook/attributes/default.rb b/test/cookbook/attributes/default.rb new file mode 100644 index 0000000..56cb0d7 --- /dev/null +++ b/test/cookbook/attributes/default.rb @@ -0,0 +1,17 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +override['poise-service']['provider'] = 'dummy' diff --git a/test/cookbook/metadata.rb b/test/cookbook/metadata.rb new file mode 100644 index 0000000..149dbeb --- /dev/null +++ b/test/cookbook/metadata.rb @@ -0,0 +1,20 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name 'application_python_test' +depends 'application_git' +depends 'application_python' +depends 'poise-build-essential' diff --git a/test/cookbook/recipes/default.rb b/test/cookbook/recipes/default.rb new file mode 100644 index 0000000..e1db35e --- /dev/null +++ b/test/cookbook/recipes/default.rb @@ -0,0 +1,85 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include_recipe 'poise-python' + +# For netstat in serverspec. +package 'net-tools' + +application '/opt/wsgi1' do + file '/opt/wsgi1/main.py' do + content <<-'EOH' +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['Hello world!\n'] +EOH + end + gunicorn do + port 8000 + end + gunicorn 'wsgi1b' do + path parent.path + service_name 'wsgi1b' + app_module 'main' + port 8001 + end +end + +application '/opt/wsgi2' do + file "#{path}/main.py" do + content <<-'EOH' +import sys +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['\n'.join(sys.path)] +EOH + end + gunicorn do + port 8002 + end +end + +application '/opt/wsgi3' do + file "#{path}/requirements.txt" do + content <<-EOH +requests +six +EOH + end + virtualenv + pip_requirements + file "#{path}/main.py" do + content <<-'EOH' +import sys +import requests +def application(environ, start_response): + status = '200 OK' + response_headers = [('Content-type', 'text/plain')] + start_response(status, response_headers) + return ['\n'.join(sys.path)] +EOH + end + gunicorn do + port 8003 + end +end + +include_recipe '::django' +include_recipe '::flask' diff --git a/test/cookbook/recipes/django.rb b/test/cookbook/recipes/django.rb new file mode 100644 index 0000000..c115174 --- /dev/null +++ b/test/cookbook/recipes/django.rb @@ -0,0 +1,36 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include_recipe 'poise-build-essential' +include_recipe 'poise-python' + +application '/opt/test_django' do + git 'https://github.com/poise/test_django.git' + python 'pypy3-5.7' + virtualenv + pip_requirements + django do + database 'sqlite:///test_django.db' + migrate true + end + gunicorn do + port 9000 + end + # PyPy is a bit slower to shut down. + poise_service_options '/opt/test_django' do + restart_delay 30 + end +end diff --git a/test/cookbook/recipes/flask.rb b/test/cookbook/recipes/flask.rb new file mode 100644 index 0000000..59d98f2 --- /dev/null +++ b/test/cookbook/recipes/flask.rb @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include_recipe 'poise-python' + +application '/opt/test_flask' do + git 'https://github.com/poise/test_flask.git' + pip_requirements + gunicorn do + port 9001 + end +end diff --git a/test/docker/docker.ca b/test/docker/docker.ca new file mode 100644 index 0000000..f381108 --- /dev/null +++ b/test/docker/docker.ca @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIJAJTJgn9tSdKmMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV +BAMTAkNBMB4XDTE1MDExMjIwMjk0M1oXDTI1MDEwOTIwMjk0M1owDTELMAkGA1UE +AxMCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDPFn79sz1kQLk +rAS5z/zfCMXuE+V9IEmGXJeSprsXrv+AdjFpVDr52lpvZ36i2gixk8grcWtFqQMC +jB1c2HUe69ebC89rHSPmGCx5eRcWQPQG29fdH/nC+I34EbYadJB7PdzkvTj0KuN8 +YfQj6lhwqltYiELZhuGoXcuhwZ5SC4VcJ2cdvx7oQPECLlMft8dhWyk15WGhp0jL +2H5noGajz9IFzHieoKJyh+oYA3BYCugpNBLTweNw+NuRxMwHixftvkXvlqKeZ402 +4iwmIO8MG9oUxXs6D85gv6tJOau+dD1EDYH9VzYwSvLto3QBzJX0NiHKlmeq4BG2 +1V3n+N1kZmMDgEtX2TDFsGHlUo77Gw0ob/w7qJU+7GAXRwWw7TPhMBLSkOlGM726 +Lq9p5+7mK1YThk0PmlsSAU6/fT79PSdrYpTKr3WkBTnwd76df+Koh8fpN4BHf9L4 +9bWSYc9Nb9/wp0md9xhzjjVHargpVxZmNH7bcIa8YA9tYaW+oifo2hfb7o0qhGQ1 +8pES3LPUi/qtZkBYUQdh8/mkqTvRjeS446iUmYWcrHyiIzQk/cMbrAVYOi3Xnq0J +ui/r48iv7uLhpcDEQl2mENr0syygrPthVKa4gYHAZ0tK3pfe0yUGMiwS2D23xMR4 +WYLWLwYSK0j1JYpEbsBNS3wZX91FIwIDAQABo24wbDAdBgNVHQ4EFgQU1j2CHhNt +sWAvDmu49yRFfHRBp9kwPQYDVR0jBDYwNIAU1j2CHhNtsWAvDmu49yRFfHRBp9mh +EaQPMA0xCzAJBgNVBAMTAkNBggkAlMmCf21J0qYwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAgEAD7apefon65k3Xey7vsTb/A18m7JwBNLB48ILNcSKVgO1 +iuSMCGNQ+4bNU4o9cTpRoijB3w4RY7IIaDlRcUg8FIO6kgEhjhiAjSSqJAaajOFc +urxOmi9E7xYmTDqLxEGF5/5vaG4olAi3tRgZNd2+Ue0ANZ1KMh3ZkE0nA5v1zb/g +Ax/Zs6tATdoG6umMQg8TjiKucwi9J9he+xJ5y0E77/RrdNL9aKcU47wTAwUkokpb +u1JFo1da3yZLDwQuBN5DCc4pgPgxXlfa6DnzQM1veKIhP5sa9T4sCC8S4IjGFenw +yl4xm+9AOZQeLFpczqgVJhun5P41syepnZ433hWoLXKLHd1n0ILgw9JyVF686LIt +bSar3+krmFuzdRCfet0kJR762p8jmxJOwL+KQGELGlkleJK48a+ruWIeeulZhpJ5 +tF4QJxytq4aXpjeFma0Yi/0rQuNi3H1QIW5YPnFL0XlJ8Rvr8gSVc1zhkM9rsnxX +l9Pun0flP/mf/ulOa020hQUPqEYjSfdJOkLy2gZDvHRL2LRXNjGHoteGNJCq34Q1 +wQerxofHn+Hpp61+Ebj+RLK0KJE+QeP3T8rL30aSSzQZQZJVI0ict5C71kiTbQnw +Z0vE6LquvFfMSqfPLt6uuCRVywBjLx19B7TuMf/DgAD+lR+1FFGKy1hO2Q1jfCY= +-----END CERTIFICATE----- diff --git a/test/docker/docker.pem b/test/docker/docker.pem new file mode 100644 index 0000000..d449b2c --- /dev/null +++ b/test/docker/docker.pem @@ -0,0 +1,83 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIBGzANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDEwJDQTAe +Fw0xNTA3MjIwMDUyNTBaFw0yNTA3MTkwMDUyNTBaMGwxLTArBgNVBAMUJERvY2tl +ciBjbGllbnQgZm9yIGFwcGxpY2F0aW9uX3B5dGhvbjEXMBUGA1UECxMOa2l0Y2hl +bi1kb2NrZXIxIjAgBgNVBAoTGUNvZGVyYW5nZXIgQ29uc3VsdGluZyBMTEMwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsshKxwkNqBbhxyUKn+1z/ZWkW +Zee7IW3uYkxBnsbyXrLKMdW3ClSSvyR6YV4mL+TmfeWYR8bZBQuzABpoNa4wLaFa +JsKUAlnAVNIOP2fd1JfBk2AmqqOx9ZL3JrQv+zYZGx5HeBC1i1vZllWio0qvToMS +OEHkacOB+YvvjCEWAm3dI8vTtGN9S1J8Ql7OtpgqgZfRVIKsPK1fqnJSis9BYzMz +B5kcLI77rs5SYuLIpA7A/t6ZhciK+tOhjlHS2ctLQJ7/5R1GgppWzvxcgmfLV3Vm +a+vFYN0xwb0MQAzXgV149yk+Ap6D4aB1riUw+YJ+zhUXIzo7xteqeMw8pkgdJ9PS +IC3liPDPKGtkyzzdoS3Wmg1MZ1pdnpvFzDct+LYO5czNIbsQGO2OasoAtVjM8ZaF +ZVh0jt4LMYJZvyCkS1FF/C8DM02R26j2hHYwUhQPn5FwzPBmTzoQwihmQIcLCPil +R9YC3SQzbZ759NXSX2HQC83msP/cPVxyilXYNgY9g8sbSBR79NgZs1oYL6xvjbw3 +sTSYFoqpXlDi4b2Atna4gP1jGmCKMlkrA8Wad+QGe/Hfsds9u+fdl/f4f4WYNHQ2 +C6goJn2L76rUya0E7VDvWqfMVyV3RE7oiMPvglsCqFx1Dp8rtSEXt6Y3yQ5uMKZN +27El6ybLXZeMlTnVQQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq +hkiG9w0BAQUFAAOCAgEASHsNNYaBZ/+D2nHj7wliHU8SQfaXdFrJ1Ei5pGU0Laqb +z2rmtTeFjzq4bBNr6K6fjMEiT5SftTEOMpteCfWEzgHLEvkR9klEMD9es4BKoKkC +aCLLkKGv9135X+rZYWDaWMoksXNC61zVFziRExVa7HZSCiTXM9Hi6A0B9U+iCC4k +Y6era2G2yHSs1JCIWQlOHHLIfk79mtEwd84mfizYjx3Bj93ZTZXESFiRx/2/YbaI +Rr+g24n4zytWXaQb2ocb3HYmvTUkv1keJnaIGJZZFW9rePaRI3f6oVA15qsvwttz +F3UQ4ACpYUd0t2nUha+trFh3Srr2e3KQz8L/10gy6riLW/n2P6AmI6Ukmz/ChIoJ +ZydvbTdwJ0LCg89EFjUQThRszce66Cbd15awZjurfpQymgn1Sa2OXAzwUTuSc2Jr +x5+leiixvudRvfk83TQnabbbdlYY5EoJpjR7iqhgH7ONIl7xUn9WC2DXK99CeWQB +WX10OLXocksL+13IAYdAeLDDIaMvDInm8GZLhdlm+HRga2E30sVq9BwRywsLOP3C +3B2JOPLo4pZdAKOL7aaLI5kDOVuQ8NW/jCuc1JSrca1OSHHEtQ18YyYfsvcfzOdu +MlB8/TF5SzuMiXKo2k1wxDTwh7+ju2tZOc3jTgZCzLYNMn/ebYVivr/MIFwv4cw= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,50BDE04B335259A3D960185E8205D667 + +eeue1i1tlmzZy3QY21NNLy+m9Bh8BJY9+Bwx+pAC8L7mwSXZdMgESBw6ta41RVoR +DAzT746Bf9tvI7tRSW1XAdY+TV2cRBueIMwm3tkumAqfxVZhWxS2XHyG5lvX1Jik +UBWDNlSnqn8w/uE7jsxnAA4LphreseQoqvOp+WTL1sempOvVW+QyyltxfmTD1pAJ +U6omrTm9BO5LNv7Uri1sRRHTTE1ejXb8xJYt8YQQKjxn2JHap6I0X+nHQ8XGdTY4 +0O9TYGCnar0LHOvvWWAr/Z4DMf0YzjS8yptrJ+C2BIIZF+4ifrW8LrBmK5/X9LFI ++oAW9Tv8sdFYtpZpXheGgFP3dt9HhD7XgnQZ7d8+3hzHELAN+U0YyvWgnmktdBDp +6l0ifgexM121mGbG6JfyAhglKuqGwal+KttmAS4NL8m4ushzlsquUO6fHsYzXrHi +4+NcbJZ2udIn1MZymicztNBRCvGTjKYRgWwl8HG/pHdJ8i8w95xI+yYKwv46OG26 +unk3KrwSm/pc+Ztu+7WqPSjmOB8wsBGbXqaI4CyeM6Tj/5HPE6Mk69Fwk8moas4f +19eA9vzwnMKmyawLFTZ1TRZ3my4GIF1qWglIgtbux8B4Daq1DN9rTDXIroE8XJXL +5h9aHmI3vweslNYRTtLvkN1JCkY71FMv1QoK3FYoAknW0BFosDPG5fJXaiEMVd2c +avD5+CgTBg9T8n3InKR2D3Xae+4PxHlsxMa65GTq303hikcrjlYoEM6zUsb77SOt +W6wuzKPyJtDJzVFt6dIf6nkynxy9ouzRS3tx4XS6dCK+R9Xe0IPcYJiP/8/dmGUx +20qrf8RHCbbSJw8+ou9qT4QnJhExKIifSKmkpdzRxAnFz5p35Fl/wMUSL9QSQjW/ +jpOUEAtGVZsmR2wqmjQdrfLydwHAG1DnOuljHwpFuJ5QgQfXH+pvq7PmyVH0r4cM ++NdUUIOeKQXX0z3nVWIXhPyqUa6nPGRxF+KySspCae6ehkrXA3sV//955WL0YBF3 +v6JNBLN9FQnO86EfJsMvVN3X23tKb9j2DvmWITFqgJT4HJvBNLVfRUyxYW/9sAO6 +RUjoDq9nnwk7U2WVrCVuOEwx2D51AQebY2pB9uvxynC7sIF9AMQ4a+wIR5T7gM/V +m5hIC54ARWE3ReJRsRKxqygU1GE0OQLB6Zq2HNRUM6CyHFULoKp0EJfJE23u2B0S +6n2u+AFRHb37Db8Kv5pInbyPsij/eX6Pis3iOE+qa4Az3wZuYd3Y8Ouiu2SeSKbv +9ThjvkU2cvWcIEKYtSuH6JXTbFtyrnxT3djdYu8puPl6LjHsSCMZyPLN2JOLVpuk +rtVm9qhZwv3e6QoDRWrtJd4h3GOYolh+f6MIa3em/uLZCz816mAgRksuI53PI4Dv +OQG2MwZdFh2b9d+iegKwsTxq6SbULUD/aagEapM5Yn05JPaCUSdzjPnTSS0YDcps +Bf4RFW8MTPLxkYrc77EZwK8ncMs9xyY7YOfbA3+tYHeBy/NfQxGv70Dz8+gcLACY +6/1xAvD2pkhvCKal3KtJ8vjIhZURdLgc+dNaPStjTY2PwzGQcimucE1ksOh+WGX+ +L/4cYWYzSVD4NzOly+73Bf0cghs9lKUYHS3PA+GCwN4QgNFTRqNHDdJdd2IQjLPn +36ZKmv1Nby1ywxsWQ4vmxOwpN8tPEVoK6T1PsVh07fxnDpuy2dUsWTh+BxauLrfq +iCpLAyHsb9es+gjqWPUNnCPWDnVjUvwHcMIyfyR/Kq2KXI46uDNvKblVhV2MiZb+ +WukdXU91ZWbngd8YJTWQbedPvKDg/ifuAr7I5xpnhlr0GkdnV460l4+5am+N3wiS +UWl26tiivV8yFHIQtX0tmMUlGPDkTvYG71aa++MVZDg5kqQvSgBmxAs4Z8yUEU7N +d+hwKOIyEQVXALWLoKG+NZ4fga3yN6rY95qpsVrIJQbleSig034okc53rfVat1D6 +X9neZ1Ey8DBIheeqvwih6Xz/xxl2XruF+N6AViknagqfm9K9T2tilQt7IE253BYZ +OKwkMOfR4zEuRa8+hejImALEOHxKcCPoYzY1uXHNGGAE8jRHGyqsGXdIcifXQErw +NVY+UYOlVyGj3QPyUSmAYN51QUanJzd7PO3b8rdNOzX43RfyiKmECyMawUa3k505 +6Epa5TYNBx4rL7DHpoE4eqMogiVpgw6YHMNXjUvJFTqX+zI0AAADNI7U+M0YLoAs +b7Vlqji7cAIo8/HFyAY4Hmj2lq0D73sDd/LYB6dMY0br5qkhqVnINMBidN1U0oZc +YbdBPAMBqZkCtEUyfHeQ76HHyXqFYfiRZLzRD0r/2OtrH6VGYu4Iu41QjQbO8k3m +vAnTMh8xCEa+t+zQWu36iZkNoUnI+KtQTMHovYQ6sH6mHFNRSfx0ElLenqXpZ4xT +HdPIsgAdIZdFApQe+IAsdmcg0GvPYjs9AqjDuT91lXrnpRcPxTFjfH7nz/3aKaOO +7s0JPE9D5SSvl6zH7rN8yK6sXPSKojJFs0jHhn710yyoortkIej8usgkimDaYgrv +bOF1RNO0gi03aiqGvKI0pEOWpKrSAXONcqUniv1K+ttlGurgTyJZmHhwwLIFVdus +juD6PXkxjkX6L9R55+PLhaGOpBP9JSxb4gXGjX9Qbn6cuNEw7IBDssR0ifvxjXCu +wl1jbW0TaO3ol+C1f2/cKLXSzfnAF7WXf1mR6yUTn8cT8mS7yWaHJFoUDwUPA99l +9b18paZR+EwZtsR31pjijnDHwycsH75nsLTddVUc7yYptgmXtHuHyZY3FT7HixD7 +Zlwz30watxcBEopb7eq0ExSY4Ri+L9rV5gBYRzazlBM7J3xN1K8sAdkReclx5ms4 +1Ez/hv7m2xkNysPpRO8SCGoMjr/UxYL+kFI/1Y/DvvSx1EpX9SkoaAF5YB7Z89+N +uTD7PFVmqS0ZFUcXvrxGi1TGEvCWHsA6NY1wDNfzI7FiZCx1E0YjTWGjkdw5d/b3 +eCfhBCAQHJHC2/PX4s8MdgyAB/jatTjglCjAYPXO7PZqyOe1RyP7pxhd2eqEbJH3 +6/keQNj+O/TdUG47LUI2D/uZ92hNRenvuu5cMhRrUivBYhiw0X3w0bidGFSw5O/h +-----END RSA PRIVATE KEY----- diff --git a/test/gemfiles/chef-12.1.gemfile b/test/gemfiles/chef-12.1.gemfile new file mode 100644 index 0000000..83ee246 --- /dev/null +++ b/test/gemfiles/chef-12.1.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.1.2' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.10.gemfile b/test/gemfiles/chef-12.10.gemfile new file mode 100644 index 0000000..b3f1bce --- /dev/null +++ b/test/gemfiles/chef-12.10.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.10.24' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.11.gemfile b/test/gemfiles/chef-12.11.gemfile new file mode 100644 index 0000000..e1a1b77 --- /dev/null +++ b/test/gemfiles/chef-12.11.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.11.18' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.12.gemfile b/test/gemfiles/chef-12.12.gemfile new file mode 100644 index 0000000..cdbf048 --- /dev/null +++ b/test/gemfiles/chef-12.12.gemfile @@ -0,0 +1,23 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.12.15' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' diff --git a/test/gemfiles/chef-12.13.gemfile b/test/gemfiles/chef-12.13.gemfile new file mode 100644 index 0000000..76b2217 --- /dev/null +++ b/test/gemfiles/chef-12.13.gemfile @@ -0,0 +1,23 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.13.37' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' diff --git a/test/gemfiles/chef-12.14.gemfile b/test/gemfiles/chef-12.14.gemfile new file mode 100644 index 0000000..e0cb5c1 --- /dev/null +++ b/test/gemfiles/chef-12.14.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.14.89' diff --git a/test/gemfiles/chef-12.15.gemfile b/test/gemfiles/chef-12.15.gemfile new file mode 100644 index 0000000..1095dd8 --- /dev/null +++ b/test/gemfiles/chef-12.15.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.15.19' diff --git a/test/gemfiles/chef-12.16.gemfile b/test/gemfiles/chef-12.16.gemfile new file mode 100644 index 0000000..601a96c --- /dev/null +++ b/test/gemfiles/chef-12.16.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.16.42' diff --git a/test/gemfiles/chef-12.17.gemfile b/test/gemfiles/chef-12.17.gemfile new file mode 100644 index 0000000..2b2dbab --- /dev/null +++ b/test/gemfiles/chef-12.17.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.17.44' diff --git a/test/gemfiles/chef-12.18.gemfile b/test/gemfiles/chef-12.18.gemfile new file mode 100644 index 0000000..eef6f42 --- /dev/null +++ b/test/gemfiles/chef-12.18.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.18.31' diff --git a/test/gemfiles/chef-12.19.gemfile b/test/gemfiles/chef-12.19.gemfile new file mode 100644 index 0000000..62c4b98 --- /dev/null +++ b/test/gemfiles/chef-12.19.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.19.36' diff --git a/test/gemfiles/chef-12.2.gemfile b/test/gemfiles/chef-12.2.gemfile new file mode 100644 index 0000000..1be9dc1 --- /dev/null +++ b/test/gemfiles/chef-12.2.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.2.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.3.gemfile b/test/gemfiles/chef-12.3.gemfile new file mode 100644 index 0000000..f467b24 --- /dev/null +++ b/test/gemfiles/chef-12.3.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.3.0' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.4.gemfile b/test/gemfiles/chef-12.4.gemfile new file mode 100644 index 0000000..3982e37 --- /dev/null +++ b/test/gemfiles/chef-12.4.gemfile @@ -0,0 +1,25 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.4.3' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'gh', '0.14.0' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.5.gemfile b/test/gemfiles/chef-12.5.gemfile new file mode 100644 index 0000000..cce7846 --- /dev/null +++ b/test/gemfiles/chef-12.5.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.5.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.6.gemfile b/test/gemfiles/chef-12.6.gemfile new file mode 100644 index 0000000..5f699c5 --- /dev/null +++ b/test/gemfiles/chef-12.6.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.6.0' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.7.gemfile b/test/gemfiles/chef-12.7.gemfile new file mode 100644 index 0000000..8eb0df8 --- /dev/null +++ b/test/gemfiles/chef-12.7.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.7.2' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.8.gemfile b/test/gemfiles/chef-12.8.gemfile new file mode 100644 index 0000000..cf1531f --- /dev/null +++ b/test/gemfiles/chef-12.8.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.8.1' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.9.gemfile b/test/gemfiles/chef-12.9.gemfile new file mode 100644 index 0000000..4295ec5 --- /dev/null +++ b/test/gemfiles/chef-12.9.gemfile @@ -0,0 +1,24 @@ +# +# Copyright 2016-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.9.41' +gem 'chefspec', '< 6' +gem 'fauxhai', '<= 3.9.0' +gem 'ffi-yajl', '< 2.3.1' +gem 'foodcritic', '< 8' +gem 'rack', '< 2' diff --git a/test/gemfiles/chef-12.gemfile b/test/gemfiles/chef-12.gemfile new file mode 100644 index 0000000..bd22a0f --- /dev/null +++ b/test/gemfiles/chef-12.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 12.19' diff --git a/test/gemfiles/chef-13.0.gemfile b/test/gemfiles/chef-13.0.gemfile new file mode 100644 index 0000000..7e864da --- /dev/null +++ b/test/gemfiles/chef-13.0.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.0.118' diff --git a/test/gemfiles/chef-13.1.gemfile b/test/gemfiles/chef-13.1.gemfile new file mode 100644 index 0000000..05668a3 --- /dev/null +++ b/test/gemfiles/chef-13.1.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.1.31' diff --git a/test/gemfiles/chef-13.2.gemfile b/test/gemfiles/chef-13.2.gemfile new file mode 100644 index 0000000..a466fdb --- /dev/null +++ b/test/gemfiles/chef-13.2.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.2.20' diff --git a/test/gemfiles/chef-13.gemfile b/test/gemfiles/chef-13.gemfile new file mode 100644 index 0000000..3f49de1 --- /dev/null +++ b/test/gemfiles/chef-13.gemfile @@ -0,0 +1,19 @@ +# +# Copyright 2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', '~> 13.2' diff --git a/test/gemfiles/master.gemfile b/test/gemfiles/master.gemfile new file mode 100644 index 0000000..182ae64 --- /dev/null +++ b/test/gemfiles/master.gemfile @@ -0,0 +1,33 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +eval_gemfile File.expand_path('../../../Gemfile', __FILE__) + +gem 'chef', git: 'https://github.com/chef/chef.git' +gem 'chefspec', git: 'https://github.com/sethvargo/chefspec.git' +gem 'fauxhai', git: 'https://github.com/customink/fauxhai.git' +gem 'foodcritic', git: 'https://github.com/foodcritic/foodcritic.git' +gem 'halite', git: 'https://github.com/poise/halite.git' +gem 'ohai', git: 'https://github.com/chef/ohai.git' +gem 'poise', git: 'https://github.com/poise/poise.git' +gem 'poise-application', git: 'https://github.com/poise/application.git' +gem 'poise-application-git', git: 'https://github.com/poise/application_git.git' +gem 'poise-archive', git: 'https://github.com/poise/poise-archive.git' +gem 'poise-boiler', git: 'https://github.com/poise/poise-boiler.git' +gem 'poise-languages', git: 'https://github.com/poise/poise-languages.git' +gem 'poise-profiler', git: 'https://github.com/poise/poise-profiler.git' +gem 'poise-python', git: 'https://github.com/poise/poise-python.git' +gem 'poise-service', git: 'https://github.com/poise/poise-service.git' diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 0000000..7d56643 --- /dev/null +++ b/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,81 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'wsgi1' do + describe port(8000) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8000) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq "Hello world!\n" } + end +end + +describe 'wsgi1b' do + describe port(8001) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8001) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq "Hello world!\n" } + end +end + +describe 'wsgi2' do + describe port(8002) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8002) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include '/opt/wsgi2' } + its(:body) { is_expected.to include '/lib/python2.7' } + its(:body) { is_expected.to match %r'/(opt/rh|usr)/.*?/lib/python2.7/(site|dist)-packages' } + end +end + +describe 'wsgi3' do + describe port(8003) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 8003) } + + describe '/' do + subject { http.get('/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include '/opt/wsgi3' } + its(:body) { is_expected.to include '/lib/python2.7' } + its(:body) { is_expected.to include '/opt/wsgi3/.virtualenv/lib/python2.7' } + its(:body) { is_expected.to_not match %r'/(opt/rh|usr)/.*?/lib/python2.7/(site|dist)-packages' } + end +end diff --git a/test/integration/default/serverspec/django_spec.rb b/test/integration/default/serverspec/django_spec.rb new file mode 100644 index 0000000..b9d8380 --- /dev/null +++ b/test/integration/default/serverspec/django_spec.rb @@ -0,0 +1,56 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'django' do + describe port(9000) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 9000) } + + describe '/foo' do + subject { http.get('/foo') } + its(:code) { is_expected.to eq '404' } + end + + describe '/admin/login/' do + subject { http.get('/admin/login/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'Polls Administration' } + end + + describe '/polls/' do + subject { http.get('/polls/') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'No polls are available.' } + end + + describe '/static/polls/style.css' do + subject { http.get('/static/polls/style.css') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to include 'color: green;' } + end + + describe '/static/polls/images/background.gif' do + subject { http.get('/static/polls/images/background.gif') } + its(:code) { is_expected.to eq '200' } + end +end diff --git a/test/integration/default/serverspec/flask_spec.rb b/test/integration/default/serverspec/flask_spec.rb new file mode 100644 index 0000000..8c0d0f2 --- /dev/null +++ b/test/integration/default/serverspec/flask_spec.rb @@ -0,0 +1,39 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'net/http' + +require 'serverspec' +set :backend, :exec + +describe 'flask' do + describe port(9001) do + it { is_expected.to be_listening } + end + + let(:http) { Net::HTTP.new('localhost', 9001) } + + describe '/foo' do + subject { http.get('/foo') } + its(:code) { is_expected.to eq '404' } + end + + describe '/hi' do + subject { http.get('/hi') } + its(:code) { is_expected.to eq '200' } + its(:body) { is_expected.to eq 'Hello World!' } + end +end diff --git a/test/spec/app_mixin_spec.rb b/test/spec/app_mixin_spec.rb new file mode 100644 index 0000000..2e34119 --- /dev/null +++ b/test/spec/app_mixin_spec.rb @@ -0,0 +1,69 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'poise_application/cheftie' +require 'poise_python/cheftie' + +describe PoiseApplicationPython::AppMixin do + describe '#parent_python' do + resource(:poise_test) do + include described_class + end + provider(:poise_test) + + context 'with an app_state python' do + recipe do + python_runtime 'outer' + application '/test' do + app_state[:python] = PoisePython::Resources::PythonRuntime::Resource.new('inner', run_context) + poise_test + end + python_runtime 'after' + poise_test 'after' + application '/other' + poise_test 'other' + end + let(:python) { chef_run.application('/test').app_state[:python] } + + it { is_expected.to run_poise_test('/test').with(parent_python: python) } + it { is_expected.to run_poise_test('after').with(parent_python: python) } + it { is_expected.to run_poise_test('other').with(parent_python: chef_run.python_runtime('after')) } + it { expect(python).to be_a Chef::Resource } + end # /context with an app_state python + + context 'with a global python' do + recipe do + python_runtime 'outer' + application '/test' do + poise_test + end + end + + it { is_expected.to run_poise_test('/test').with(parent_python: chef_run.python_runtime('outer')) } + end # /context with a global python + + context 'with no python' do + recipe do + application '/test' do + poise_test + end + end + + it { is_expected.to run_poise_test('/test').with(parent_python: nil) } + end # /context with no python + end # /describe #parent_python +end diff --git a/test/spec/resources/celery_config_spec.rb b/test/spec/resources/celery_config_spec.rb new file mode 100644 index 0000000..846c221 --- /dev/null +++ b/test/spec/resources/celery_config_spec.rb @@ -0,0 +1,58 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::CeleryConfig do + step_into(:application_celery_config) + before do + allow(File).to receive(:directory?).and_call_original + allow(File).to receive(:directory?).with('/test').and_return(true) + end + + context 'with defaults' do + recipe do + application_celery_config '/test' + end + it { is_expected.to deploy_application_celery_config('/test').with(path: '/test/celeryconfig.py') } + it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) } +# Generated by Chef for application_celery_config[/test] + +CELERYCONFIG + end # /context with defaults + + context 'with a specific path' do + recipe do + application_celery_config '/test/foo.py' + end + it { is_expected.to deploy_application_celery_config('/test/foo.py').with(path: '/test/foo.py') } + end # /context with a specific path + + context 'with template options' do + recipe do + application_celery_config '/test' do + options do + broker_url 'amqp://' + end + end + end + it { is_expected.to render_file('/test/celeryconfig.py').with_content(eq(<<-CELERYCONFIG)) } +# Generated by Chef for application_celery_config[/test] + +BROKER_URL = "amqp://" +CELERYCONFIG + end # /context with template options +end diff --git a/test/spec/resources/django_spec.rb b/test/spec/resources/django_spec.rb new file mode 100644 index 0000000..dccbd3c --- /dev/null +++ b/test/spec/resources/django_spec.rb @@ -0,0 +1,303 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Django do + describe PoiseApplicationPython::Resources::Django::Resource do + describe '#local_settings' do + subject { chef_run.application_django('/test').local_settings_content } + + context 'with defaults' do + recipe(subject: false) do + application_django '/test' + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with defaults + + context 'with a URL' do + recipe(subject: false) do + application_django '/test' do + database 'postgres://myuser@dbhost/myapp' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{"URL":"postgres://myuser@dbhost/myapp","ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}} +SETTINGS + end # /context with a URL + + context 'with an options block' do + recipe(subject: false) do + application_django '/test' do + database do + engine 'postgres' + name 'myapp' + user 'myuser' + host 'dbhost' + end + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{"ENGINE":"django.db.backends.postgresql_psycopg2","NAME":"myapp","USER":"myuser","HOST":"dbhost"}} +SETTINGS + end # /context with an options block + + context 'with debug mode' do + recipe(subject: false) do + application_django '/test' do + debug true + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = True + +DATABASES = {"default":{}} +SETTINGS + end # /context with debug mode + + context 'with a single allowed host' do + recipe(subject: false) do + application_django '/test' do + allowed_hosts 'example.com' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +ALLOWED_HOSTS = ["example.com"] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with a single allowed host + + context 'with multiple allowed hosts' do + recipe(subject: false) do + application_django '/test' do + allowed_hosts %w{example.com www.example.com} + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +ALLOWED_HOSTS = ["example.com","www.example.com"] + +DEBUG = False + +DATABASES = {"default":{}} +SETTINGS + end # /context with multiple allowed hosts + + context 'with a secret key' do + recipe(subject: false) do + application_django '/test' do + secret_key 'swordfish' + end + end + it { is_expected.to eq <<-SETTINGS } +# Generated by Chef for application_django[/test] + +DEBUG = False + +DATABASES = {"default":{}} + +SECRET_KEY = "swordfish" +SETTINGS + end # /context with a secret key + end # /describe #local_settings + + describe '#default_local_settings_path' do + subject { chef_run.application_django('/test').send(:default_local_settings_path) } + + context 'with no settings.py' do + recipe(subject: false) do + application_django '/test' do + def settings_module + nil + end + end + end + it { is_expected.to be_nil } + end # /context with no settings.py + + context 'with basic settings.py' do + recipe(subject: false) do + application_django '/test' do + settings_module 'myapp.settings' + end + end + it { is_expected.to eq '/test/myapp/local_settings.py' } + end # /context with basic settings.py + end # /describe #default_local_settings_path + + describe '#default_manage_path' do + subject { chef_run.application_django('/test').send(:default_manage_path) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('manage.py').and_return('/test/manage.py') + end + + it { is_expected.to eq '/test/manage.py' } + end # /describe #default_manage_path + + describe '#default_settings_module' do + let(:settings_path) { nil } + subject { chef_run.application_django('/test').send(:default_settings_module) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('settings.py').and_return(settings_path) + end + + context 'with no settings.py' do + it { is_expected.to be_nil } + end # /context with no settings.py + + context 'with simple settings.py' do + let(:settings_path) { '/test/myapp/settings.py' } + it { is_expected.to eq 'myapp.settings' } + end # /context with simple settings.py + end # /describe #default_settings_module + + describe '#default_wsgi_module' do + let(:wsgi_path) { nil } + subject { chef_run.application_django('/test').send(:default_wsgi_module) } + recipe(subject: false) do + application_django '/test' + end + before do + allow(chef_run.application_django('/test')).to receive(:find_file).with('wsgi.py').and_return(wsgi_path) + end + + context 'with no wsgi.py' do + it { is_expected.to be_nil } + end # /context with no wsgi.py + + context 'with simple wsgi.py' do + let(:wsgi_path) { '/test/wsgi.py' } + it { is_expected.to eq 'wsgi' } + end # /context with simple wsgi.py + end # /describe #default_wsgi_module + + describe '#find_file' do + let(:files) { [] } + recipe(subject: false) do + application_django '/test' + end + subject { chef_run.application_django('/test').send(:find_file, 'myfile.py') } + before do + allow(Dir).to receive(:[]).and_call_original + allow(Dir).to receive(:[]).with('/test/**/myfile.py').and_return(files) + end + + context 'with no matching files' do + it { is_expected.to be_nil } + end # /context with no matching files + + context 'with one matching file' do + let(:files) { %w{/test/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with one matching file + + context 'with two matching files' do + let(:files) { %w{/test/myfile.py /test/sub/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with two matching files + + context 'with two matching files in a different order' do + let(:files) { %w{/test/sub/myfile.py /test/myfile.py} } + it { is_expected.to eq '/test/myfile.py' } + end # /context with two matching files in a different order + + context 'with two matching files on the same level' do + let(:files) { %w{/test/b/myfile.py /test/a/myfile.py} } + it { is_expected.to eq '/test/a/myfile.py' } + end # /context with two matching files on the same level + end # /describe #find_file + end # /describe PoiseApplicationPython::Resources::Django::Resource + + describe PoiseApplicationPython::Resources::Django::Provider do + step_into(:application_django) + context 'with default settings' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + settings_module 'myapp.settings' + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to_not run_python_execute('manage.py migrate --noinput') } + it { is_expected.to render_file('/test/myapp/local_settings.py') } + end # /context with default settings + + context 'with syncdb' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + settings_module 'myapp.settings' + syncdb true + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to_not run_python_execute('manage.py migrate --noinput') } + end # /context with syncdb + + context 'with migrate' do + recipe do + application_django '/test' do + # Hardwire all paths so it doesn't have to search. + manage_path 'manage.py' + migrate true + settings_module 'myapp.settings' + wsgi_module 'wsgi' + end + end + + it { is_expected.to run_python_execute('manage.py collectstatic --noinput') } + it { is_expected.to_not run_python_execute('manage.py syncdb --noinput') } + it { is_expected.to run_python_execute('manage.py migrate --noinput') } + it { is_expected.to render_file('/test/myapp/local_settings.py') } + end # /context with migrate + end # /describe PoiseApplicationPython::Resources::Django::Provider +end diff --git a/test/spec/resources/gunicorn_spec.rb b/test/spec/resources/gunicorn_spec.rb new file mode 100644 index 0000000..c50da3b --- /dev/null +++ b/test/spec/resources/gunicorn_spec.rb @@ -0,0 +1,96 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Gunicorn do + describe PoiseApplicationPython::Resources::Gunicorn::Resource do + describe '#default_app_module' do + let(:app_state) { {} } + let(:files) { [] } + let(:test_resource) { described_class.new('/test', nil) } + before do + allow(test_resource).to receive(:app_state).and_return(app_state) + allow(Dir).to receive(:exist?).and_return(!files.empty?) + allow(Dir).to receive(:entries).and_return(files) + end + subject { test_resource.send(:default_app_module) } + + context 'with an app_state key' do + let(:app_state) { {python_wsgi_module: 'django'} } + it { is_expected.to eq 'django' } + end # /context with an app_state key + + context 'with a wsgi.py' do + let(:files) { %w{wsgi.py} } + it { is_expected.to eq 'wsgi' } + end # /context with a wsgi.py + + context 'with an app.py and main.py' do + let(:files) { %w{app.py main.py} } + it { is_expected.to eq 'main' } + end # /context with an app.py and main.py + + context 'with a foo.txt and bar.py' do + let(:files) { %w{foo.txt bar.py} } + it { is_expected.to eq 'bar' } + end # /context with a foo.txt and bar.py + + context 'with a foo.txt' do + let(:files) { %w{foo.txt } } + it { is_expected.to be_nil } + end # /context with a foo.txt + end # /describe #default_app_module + end # /describe PoiseApplicationPython::Resources::Gunicorn::Resource + + describe PoiseApplicationPython::Resources::Gunicorn::Provider do + let(:new_resource) { double('new_resource') } + let(:test_provider) { described_class.new(new_resource, nil) } + + describe '#gunicorn_command_options' do + let(:props) { {} } + let(:new_resource) { PoiseApplicationPython::Resources::Gunicorn::Resource.new('/test', nil) } + subject { test_provider.send(:gunicorn_command_options).join(' ') } + before do + props.each {|key, value| new_resource.send(key, value) } + end + + context 'with defaults' do + it { is_expected.to eq '--bind 0.0.0.0:80' } + end # /context with defaults + + context 'with a config file' do + let(:props) { {config: '/test/myconfig.py'} } + it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80' } + end # /context with a config file + + context 'with a blank config file' do + let(:props) { {config: ''} } + it { is_expected.to eq '--bind 0.0.0.0:80' } + end # /context with a blank config file + + context 'with two binds' do + let(:props) { {bind: %w{0.0.0.0:80 0.0.0.0:81}} } + it { is_expected.to eq '--bind 0.0.0.0:80 --bind 0.0.0.0:81' } + end # /context with two binds + + context 'with a config file and preload' do + let(:props) { {config: '/test/myconfig.py', preload_app: true} } + it { is_expected.to eq '--config /test/myconfig.py --bind 0.0.0.0:80 --preload' } + end # /context with a config file and preload + end # /describe #gunicorn_command_options + end # /describe PoiseApplicationPython::Resources::Gunicorn::Provider +end diff --git a/test/spec/resources/python_execute_spec.rb b/test/spec/resources/python_execute_spec.rb new file mode 100644 index 0000000..29107f8 --- /dev/null +++ b/test/spec/resources/python_execute_spec.rb @@ -0,0 +1,52 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::PythonExecute do + step_into(:application_python_execute) + recipe do + application '/srv/myapp' do + owner 'myuser' + group 'mygroup' + environment ENVKEY: 'ENVVALUE' + + python('') { provider :dummy } + python_execute 'myapp.py' + end + end + + it do + # Check which method to stub. I'm not super proud of this code, sorry. + method_to_stub = if IO.read(described_class::Provider.instance_method(:action_run).source_location.first) =~ /shell_out_with_systems_locale/ + :shell_out_with_systems_locale! + else + :shell_out! + end + expect_any_instance_of(described_class::Provider).to receive(method_to_stub).with( + '/python myapp.py', + user: 'myuser', + group: 'mygroup', + cwd: '/srv/myapp', + timeout: 3600, + returns: 0, + environment: {'ENVKEY' => 'ENVVALUE'}, + log_level: :info, + log_tag: 'application_python_execute[myapp.py]', + ) + is_expected.to run_application_python_execute('myapp.py').with(user: 'myuser', group: 'mygroup', cwd: '/srv/myapp') + end +end diff --git a/test/spec/resources/python_spec.rb b/test/spec/resources/python_spec.rb new file mode 100644 index 0000000..6e5ff3c --- /dev/null +++ b/test/spec/resources/python_spec.rb @@ -0,0 +1,44 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Python do + shared_examples 'application_python' do + it { is_expected.to install_application_python('ver') } + it { expect(chef_run.application('/test').app_state[:python]).to eq chef_run.application_python('ver') } + end # /shared_examples application_python + + context 'with #python_runtime' do + recipe do + application '/test' do + python_runtime 'ver' + end + end + + it_behaves_like 'application_python' + end # /context with #python_runtime + + context 'with #python' do + recipe do + application '/test' do + python 'ver' + end + end + + it_behaves_like 'application_python' + end # /context with #python +end diff --git a/test/spec/resources/virtualenv_spec.rb b/test/spec/resources/virtualenv_spec.rb new file mode 100644 index 0000000..082d2e7 --- /dev/null +++ b/test/spec/resources/virtualenv_spec.rb @@ -0,0 +1,44 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +describe PoiseApplicationPython::Resources::Virtualenv do + shared_examples 'application_virtualenv' do + it { is_expected.to create_application_virtualenv('/test').with(path: '/test/.virtualenv') } + it { expect(chef_run.application('/test').app_state[:python]).to eq chef_run.application_virtualenv('/test') } + end # /shared_examples application_virtualenv + + context 'with #python_virtualenv' do + recipe do + application '/test' do + python_virtualenv + end + end + + it_behaves_like 'application_virtualenv' + end # /context with #python_virtualenv + + context 'with #virtualenv' do + recipe do + application '/test' do + virtualenv + end + end + + it_behaves_like 'application_virtualenv' + end # /context with #virtualenv +end diff --git a/test/spec/spec_helper.rb b/test/spec/spec_helper.rb new file mode 100644 index 0000000..2ed212f --- /dev/null +++ b/test/spec/spec_helper.rb @@ -0,0 +1,19 @@ +# +# Copyright 2015-2017, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'poise_boiler/spec_helper' +require 'poise_application_python' +require 'poise_application/cheftie'