├── .gitignore ├── .kitchen.yml ├── .travis.yml ├── .yardopts ├── .yo-rc.json ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── chef ├── attributes │ └── default.rb └── recipes │ └── default.rb ├── lib ├── poise_python.rb └── poise_python │ ├── cheftie.rb │ ├── error.rb │ ├── python_command_mixin.rb │ ├── python_providers.rb │ ├── python_providers │ ├── base.rb │ ├── dummy.rb │ ├── msi.rb │ ├── portable_pypy.rb │ ├── portable_pypy3.rb │ ├── scl.rb │ └── system.rb │ ├── resources.rb │ ├── resources │ ├── pip_requirements.rb │ ├── python_execute.rb │ ├── python_package.rb │ ├── python_runtime.rb │ ├── python_runtime_pip.rb │ ├── python_runtime_test.rb │ └── python_virtualenv.rb │ ├── utils.rb │ ├── utils │ └── python_encoder.rb │ └── version.rb ├── poise-python.gemspec └── test ├── cookbook ├── metadata.rb └── recipes │ └── default.rb ├── docker ├── docker.ca └── docker.pem ├── gemfiles ├── chef-12.16.gemfile ├── chef-12.17.gemfile ├── chef-12.18.gemfile ├── chef-12.19.gemfile ├── chef-12.20.gemfile ├── chef-12.21.gemfile ├── chef-12.gemfile ├── chef-13.0.gemfile ├── chef-13.1.gemfile ├── chef-13.10.gemfile ├── chef-13.2.gemfile ├── chef-13.3.gemfile ├── chef-13.4.gemfile ├── chef-13.5.gemfile ├── chef-13.6.gemfile ├── chef-13.8.gemfile ├── chef-13.9.gemfile ├── chef-13.gemfile ├── chef-14.0.gemfile ├── chef-14.1.gemfile ├── chef-14.2.gemfile ├── chef-14.3.gemfile ├── chef-14.4.gemfile ├── chef-14.gemfile └── master.gemfile ├── integration └── default │ └── serverspec │ └── default_spec.rb └── spec ├── python_command_mixin_spec.rb ├── python_providers ├── dummy_spec.rb ├── portable_pypy3_spec.rb ├── portable_pypy_spec.rb ├── scl_spec.rb └── system_spec.rb ├── resources ├── pip_requirements_spec.rb ├── python_package_spec.rb ├── python_runtime_pip_spec.rb ├── python_runtime_spec.rb └── python_virtualenv_spec.rb ├── spec_helper.rb ├── utils └── python_encoder_spec.rb └── utils_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Berksfile.lock 2 | Gemfile.lock 3 | test/gemfiles/*.lock 4 | .kitchen/ 5 | .kitchen.local.yml 6 | test/docker/ 7 | test/ec2/ 8 | coverage/ 9 | pkg/ 10 | .yardoc/ 11 | doc/ 12 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #<% require 'poise_boiler' %> 3 | <%= PoiseBoiler.kitchen(platforms: %w{ubuntu-16.04 centos-7}) %> 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | cache: bundler 4 | language: ruby 5 | env: 6 | global: 7 | - secure: j4GV/0NXC6p/+XxgK2isMCzccR7ry8cxLJZZbJN5P+958lLGmYtPPsfAN+aRzcR3Uc/alLcsjXvceCjJWU7qYpkN1x9cn564J1X3OIOygi6xdYQIMLL5Guxp01QYabFlrmZeUnTVMTkdR1H4aRrpzW4kTuc5hbIZaJl0enEe1Dd+vbCw2GOTXFnPTeLDCaCSNXXhXmqqWD64leFR2PTwx8fwrXyCB/bDRXhFdsFA5Q/tCGW7PDaYujbqSCegpuz+uvHoM5S2k2XY3pShWZm9WcmEzCLEfhOGCc1s+ia7TZ0iV7kzEq11mNTdSl6nJtBUiKsgqw1TRPFhhUj2Bp/K+cNsd06rtFE3In2zbqbgyJ0qnyQHs2FZO//ve8bug+B0lA3i6SantruS1Fc9NjwjqzOmqmaqNhnVl1jEQTOB3JUC465QShySF0aJtjE5khD7PzGwCw6cqhPBLH5ZTmqCYvl/egYLMlvVr1Nboa+0YFdzUqoTFJpRTsu89l8+1+Eu9pvHn0CwsjWmEVHahIw/GEBjhLPDinquJNxWz1q49ozlohntbRmWhbgzz6D4wPrbBdyW7rYm8p6d2sOy6Op2zsVv4c7Dj3pcLLd9J5o6RaNxf4wLd5VFLNJR6/wldsZXGxV/iP16oyasabYIQYZNbP0U1hZiUpLQ5cvKYc6xRTM= 8 | before_install: 9 | - 'if [[ $BUNDLE_GEMFILE == *master.gemfile ]]; then gem update --system; fi' 10 | - gem --version 11 | - gem install bundler 12 | - bundle --version 13 | - 'bundle config --local path ${BUNDLE_PATH:-$(dirname $BUNDLE_GEMFILE)/vendor/bundle}' 14 | - bundle config --local bin $PWD/bin 15 | install: bundle update --jobs=3 --retry=3 16 | script: 17 | - ./bin/rake travis 18 | matrix: 19 | include: 20 | - rvm: 2.3.1 21 | gemfile: test/gemfiles/chef-12.gemfile 22 | - rvm: 2.4.3 23 | gemfile: test/gemfiles/chef-13.gemfile 24 | - rvm: 2.5.1 25 | gemfile: test/gemfiles/chef-14.gemfile 26 | - rvm: 2.5.1 27 | gemfile: test/gemfiles/master.gemfile 28 | - rvm: 2.3.1 29 | gemfile: test/gemfiles/chef-12.16.gemfile 30 | - rvm: 2.3.1 31 | gemfile: test/gemfiles/chef-12.17.gemfile 32 | - rvm: 2.3.1 33 | gemfile: test/gemfiles/chef-12.18.gemfile 34 | - rvm: 2.3.1 35 | gemfile: test/gemfiles/chef-12.19.gemfile 36 | - rvm: 2.3.1 37 | gemfile: test/gemfiles/chef-12.20.gemfile 38 | - rvm: 2.3.1 39 | gemfile: test/gemfiles/chef-12.21.gemfile 40 | - rvm: 2.4.1 41 | gemfile: test/gemfiles/chef-13.0.gemfile 42 | - rvm: 2.4.1 43 | gemfile: test/gemfiles/chef-13.1.gemfile 44 | - rvm: 2.4.1 45 | gemfile: test/gemfiles/chef-13.2.gemfile 46 | - rvm: 2.4.1 47 | gemfile: test/gemfiles/chef-13.3.gemfile 48 | - rvm: 2.4.2 49 | gemfile: test/gemfiles/chef-13.4.gemfile 50 | - rvm: 2.4.2 51 | gemfile: test/gemfiles/chef-13.5.gemfile 52 | - rvm: 2.4.2 53 | gemfile: test/gemfiles/chef-13.6.gemfile 54 | - rvm: 2.4.3 55 | gemfile: test/gemfiles/chef-13.8.gemfile 56 | - rvm: 2.4.3 57 | gemfile: test/gemfiles/chef-13.9.gemfile 58 | - rvm: 2.4.3 59 | gemfile: test/gemfiles/chef-13.10.gemfile 60 | - rvm: 2.5.1 61 | gemfile: test/gemfiles/chef-14.0.gemfile 62 | - rvm: 2.5.1 63 | gemfile: test/gemfiles/chef-14.1.gemfile 64 | - rvm: 2.5.1 65 | gemfile: test/gemfiles/chef-14.2.gemfile 66 | - rvm: 2.5.1 67 | gemfile: test/gemfiles/chef-14.3.gemfile 68 | - rvm: 2.5.1 69 | gemfile: test/gemfiles/chef-14.4.gemfile 70 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --plugin classmethods 2 | --embed-mixin ClassMethods 3 | --hide-api private 4 | --markup markdown 5 | --hide-void-return 6 | --tag provides:Provides 7 | --tag action:Actions 8 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-poise": { 3 | "name": "poise-python", 4 | "coobookName": "auto", 5 | "created": true 6 | } 7 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Poise-Python Changelog 2 | 3 | ## v1.7.1 4 | 5 | * Support for Pip 18.1. 6 | * Improved support for Python 3 on Ubuntu 18.04. 7 | * Update SCL packages. 8 | 9 | ## v1.7.0 10 | 11 | * Support for Pip 10. 12 | * Support for Chef 14. 13 | * System package info for Ubuntu 18.04 and Debian 9, 10. 14 | 15 | ## v1.6.0 16 | 17 | * Improved handling for Python 3.3. 18 | * Updated PyPy release lists. 19 | * Fix file permissions for non-root-owned virtualenvs. 20 | * Support for Chef 13. 21 | 22 | ## v1.5.1 23 | 24 | * Fix handling of packages with underscores in the name. 25 | 26 | ## v1.5.0 27 | 28 | * Support new SCL structure and packages. 29 | 30 | ## v1.4.0 31 | 32 | * Add system package names for Ubuntu 16.04. 33 | * Add `options` and `cwd` properties to `pip_requirements` resource. 34 | * Add `install_options` and `list_options` to `python_package` resource. 35 | 36 | ## v1.3.0 37 | 38 | * Don't re-bootstrap very old pip if that is the configured version. 39 | * Support for bootstrapping with a specific version of pip. 40 | * [#40](https://github.com/poise/poise-python/pulls/40) Support for Python 3 system packages on Amazon Linux. 41 | * Experimental Windows support. 42 | 43 | ## v1.2.1 44 | 45 | * Compatibility with Pip 8.0. 46 | 47 | ## v1.2.0 48 | 49 | * Add support for passing `user` and `group` to `pip_requirements`. 50 | * Allow passing a virtualenv resource object to the `virtualenv` property. 51 | * Update PyPy release versions. 52 | * Make the `python_virtualenv` resource check for `./bin/python` for idempotence 53 | instead of the base path. 54 | * Support for packages with extras in `python_package`. 55 | * Support for point releases (7.1, 8.1, etc) of Debian in the `system` provider. 56 | 57 | ## v1.1.2 58 | 59 | * Fix `PythonPackage#response_file_variables` for the Chef 12.6 initializer. 60 | 61 | ## v1.1.1 62 | 63 | * Fix passing options to the `python_package` resource. 64 | 65 | ## v1.1.0 66 | 67 | * Add a `:dummy` provider for `python_runtime` for unit testing or complex overrides. 68 | * Support installing development headers for SCL packages. 69 | * Refactor Portable PyPy provider to use new helpers from `poise-languages`. This 70 | means `portable_pypy` and `portable_pypy3` are now separate providers but the 71 | auto-selection logic should still work as before. 72 | 73 | ## v1.0.0 74 | 75 | * Initial release! 76 | 77 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | source 'https://rubygems.org/' 18 | 19 | gemspec path: File.expand_path('..', __FILE__) 20 | 21 | def dev_gem(name, path: File.join('..', name), github: nil) 22 | path = File.expand_path(File.join('..', path), __FILE__) 23 | if File.exist?(path) 24 | gem name, path: path 25 | elsif github 26 | gem name, git: "https://github.com/#{github}.git" 27 | end 28 | end 29 | 30 | dev_gem 'halite', github: 'poise/halite' 31 | dev_gem 'poise' 32 | dev_gem 'poise-archive' 33 | dev_gem 'poise-boiler' 34 | dev_gem 'poise-languages' 35 | dev_gem 'poise-profiler' 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poise-Python Cookbook 2 | 3 | [![Build Status](https://img.shields.io/travis/poise/poise-python.svg)](https://travis-ci.org/poise/poise-python) 4 | [![Gem Version](https://img.shields.io/gem/v/poise-python.svg)](https://rubygems.org/gems/poise-python) 5 | [![Cookbook Version](https://img.shields.io/cookbook/v/poise-python.svg)](https://supermarket.chef.io/cookbooks/poise-python) 6 | [![Coverage](https://img.shields.io/codecov/c/github/poise/poise-python.svg)](https://codecov.io/github/poise/poise-python) 7 | [![Gemnasium](https://img.shields.io/gemnasium/poise/poise-python.svg)](https://gemnasium.com/poise/poise-python) 8 | [![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 9 | 10 | A [Chef](https://www.chef.io/) cookbook to provide a unified interface for 11 | installing Python, managing Python packages, and creating virtualenvs. 12 | 13 | ## Quick Start 14 | 15 | To install the latest available version of Python 2 and then use it to create 16 | a virtualenv and install some packages: 17 | 18 | ```ruby 19 | python_runtime '2' 20 | 21 | python_virtualenv '/opt/myapp/.env' 22 | 23 | python_package 'Django' do 24 | version '1.8' 25 | end 26 | 27 | pip_requirements '/opt/myapp/requirements.txt' 28 | ``` 29 | 30 | ## Installing a Package From a URI 31 | 32 | While using `python_package 'git+https://github.com/example/mypackage.git'` will 33 | sometimes work, this approach is not recommended. Unfortunately pip's support 34 | for installing directly from URI sources is limited and cannot support the API 35 | used for the `python_package` resource. You can run the install either directly 36 | from the URI or through an intermediary `git` resource: 37 | 38 | ```ruby 39 | # Will re-install on every converge unless you add a not_if/only_if. 40 | python_execute '-m pip install git+https://github.com/example/mypackage.git' 41 | 42 | # Will only re-install when the git repository updates. 43 | python_execute 'install mypackage' do 44 | action :nothing 45 | command '-m pip install .' 46 | cwd '/opt/mypackage' 47 | end 48 | git '/opt/mypackage' do 49 | repository 'https://github.com/example/mypackage.git' 50 | notifies :run, 'python_execute[install mypackage]', :immediately 51 | end 52 | ``` 53 | 54 | ## Supported Python Versions 55 | 56 | This cookbook can install at least Python 2.7, Python 3, and PyPy on all 57 | supported platforms (Debian, Ubuntu, RHEL, CentOS, Fedora). 58 | 59 | ### Windows Support 60 | 61 | The latest version of `poise-python` includes basic support for managing Python 62 | on Windows. This currently doesn't support Python 3.5, but everything should be 63 | working. Consider this support tested but experimental at this time. 64 | 65 | ## Requirements 66 | 67 | Chef 12.1 or newer is required. 68 | 69 | ## Attributes 70 | 71 | Attributes are used to configure the default recipe. 72 | 73 | * `node['poise-python']['install_python2']` – Install a Python 2.x runtime. *(default: true)* 74 | * `node['poise-python']['install_python3']` – Install a Python 3.x runtime. *(default: false)* 75 | * `node['poise-python']['install_pypy']` – Install a PyPy runtime. *(default: false)* 76 | 77 | ## Recipes 78 | 79 | ### `default` 80 | 81 | The default recipe installs Python 2, 3, and/or PyPy based on the node 82 | attributes. It is entirely optional and can be ignored in favor of direct use 83 | of the `python_runtime` resource. 84 | 85 | ## Resources 86 | 87 | ### `python_runtime` 88 | 89 | The `python_runtime` resource installs a Python interpreter. 90 | 91 | ```ruby 92 | python_runtime '2' 93 | ``` 94 | 95 | #### Actions 96 | 97 | * `:install` – Install the Python interpreter. *(default)* 98 | * `:uninstall` – Uninstall the Python interpreter. 99 | 100 | #### Properties 101 | 102 | * `version` – Version of Python to install. If a partial version is given, use the 103 | latest available version matching that prefix. *(name property)* 104 | * `get_pip_url` – URL to download the `get-pip.py` bootstrap script from. 105 | *(default: https://bootstrap.pypa.io/get-pip.py)* 106 | * `pip_version` – Version of pip to install. If set to `true`, use the latest. 107 | If set to `false`, do not install pip. For backward compatibility, can also be 108 | set to a URL instead of `get_pip_url`. *(default: true)* 109 | * `setuptools_version` – Version of Setuptools to install. If set to `true`, use 110 | the latest. If set to `false`, do not install Setuptools. *(default: true)* 111 | * `virtualenv_version` – Version of virtualenv to install. If set to `true`, 112 | use the latest. If set to `false`, do not install virtualenv. Will never be 113 | installed if the `venv` module is already available, such as on Python 3. 114 | *(default: true)* 115 | * `wheel_version` – Version of wheel to install. If set to `true`, use the 116 | latest. If set to `false`, do not install wheel. 117 | 118 | #### Provider Options 119 | 120 | The `poise-python` library offers an additional way to pass configuration 121 | information to the final provider called "options". Options are key/value pairs 122 | that are passed down to the `python_runtime` provider and can be used to control how it 123 | installs Python. These can be set in the `python_runtime` 124 | resource using the `options` method, in node attributes or via the 125 | `python_runtime_options` resource. The options from all sources are merged 126 | together in to a single hash. 127 | 128 | When setting options in the resource you can either set them for all providers: 129 | 130 | ```ruby 131 | python_runtime 'myapp' do 132 | version '2.7' 133 | options pip_version: false 134 | end 135 | ``` 136 | 137 | or for a single provider: 138 | 139 | ```ruby 140 | python_runtime 'myapp' do 141 | version '2.7' 142 | options :system, dev_package: false 143 | end 144 | ``` 145 | 146 | Setting via node attributes is generally how an end-user or application cookbook 147 | will set options to customize installations in the library cookbooks they are using. 148 | You can set options for all installations or for a single runtime: 149 | 150 | ```ruby 151 | # Global, for all installations. 152 | override['poise-python']['options']['pip_version'] = false 153 | # Single installation. 154 | override['poise-python']['myapp']['version'] = 'pypy' 155 | ``` 156 | 157 | The `python_runtime_options` resource is also available to set node attributes 158 | for a specific installation in a DSL-friendly way: 159 | 160 | ```ruby 161 | python_runtime_options 'myapp' do 162 | version '3' 163 | end 164 | ``` 165 | 166 | Unlike resource attributes, provider options can be different for each provider. 167 | Not all providers support the same options so make sure to the check the 168 | documentation for each provider to see what options the use. 169 | 170 | ### `python_runtime_options` 171 | 172 | The `python_runtime_options` resource allows setting provider options in a 173 | DSL-friendly way. See [the Provider Options](#provider-options) section for more 174 | information about provider options overall. 175 | 176 | ```ruby 177 | python_runtime_options 'myapp' do 178 | version '3' 179 | end 180 | ``` 181 | 182 | #### Actions 183 | 184 | * `:run` – Apply the provider options. *(default)* 185 | 186 | #### Properties 187 | 188 | * `resource` – Name of the `python_runtime` resource. *(name property)* 189 | * `for_provider` – Provider to set options for. 190 | 191 | All other attribute keys will be used as options data. 192 | 193 | ### `python_execute` 194 | 195 | The `python_execute` resource executes a Python script using the configured runtime. 196 | 197 | ```ruby 198 | python_execute 'myapp.py' do 199 | user 'myuser' 200 | end 201 | ``` 202 | 203 | This uses the built-in `execute` resource and supports all the same properties. 204 | 205 | #### Actions 206 | 207 | * `:run` – Execute the script. *(default)* 208 | 209 | #### Properties 210 | 211 | * `command` – Script and arguments to run. Must not include the `python`. *(name attribute)* 212 | * `python` – Name of the `python_runtime` resource to use. If not specified, the 213 | most recently declared `python_runtime` will be used. Can also be set to the 214 | full path to a `python` binary. 215 | * `virtualenv` – Name of the `python_virtualenv` resource to use. This is 216 | mutually exclusive with the `python` property. 217 | 218 | For other properties see the [Chef documentation](https://docs.chef.io/resource_execute.html#attributes). 219 | 220 | ### `python_package` 221 | 222 | The `python_package` resource installs Python packages using 223 | [pip](https://pip.pypa.io/). 224 | 225 | ```ruby 226 | python_package 'Django' do 227 | version '1.8' 228 | end 229 | ``` 230 | 231 | This uses the built-in `package` resource and supports the same actions and 232 | properties. Multi-package installs are supported using the standard syntax. 233 | 234 | #### Actions 235 | 236 | * `:install` – Install the package. *(default)* 237 | * `:upgrade` – Install using the `--upgrade` flag. 238 | * `:remove` – Uninstall the package. 239 | 240 | The `:purge` and `:reconfigure` actions are not supported. 241 | 242 | #### Properties 243 | 244 | * `group` – System group to install the package. 245 | * `package_name` – Package or packages to install. *(name property)* 246 | * `version` – Version or versions to install. 247 | * `python` – Name of the `python_runtime` resource to use. If not specified, the 248 | most recently declared `python_runtime` will be used. Can also be set to the 249 | full path to a `python` binary. 250 | * `user` – System user to install the package. 251 | * `virtualenv` – Name of the `python_virtualenv` resource to use. This is 252 | mutually exclusive with the `python` property. 253 | * `options` – Options to pass to `pip`. 254 | * `install_options` – Options to pass to `pip install` (and similar commands). 255 | * `list_options` – Options to pass to `pip list` (and similar commands). 256 | 257 | For other properties see the [Chef documentation](https://docs.chef.io/resource_package.html#attributes). 258 | The `response_file`, `response_file_variables`, and `source` properties are not 259 | supported. 260 | 261 | ### `python_virtualenv` 262 | 263 | The `python_virtualenv` resource creates Python virtual environments. 264 | 265 | ```ruby 266 | python_virtualenv '/opt/myapp' 267 | ``` 268 | 269 | This will use the `venv` module if available, or `virtualenv` otherwise. 270 | 271 | #### Actions 272 | 273 | * `:create` – Create the virtual environment. *(default)* 274 | * `:delete` – Delete the virtual environment. 275 | 276 | #### Properties 277 | 278 | * `group` – System group to create the virtualenv. 279 | * `path` – Path to create the environment at. *(name property)* 280 | * `pip_version` – Version of pip to install. If set to `true`, use the latest. 281 | If set to `false`, do not install pip. Can also be set to a URL to a copy of 282 | the `get-pip.py` script. *(default: true)* 283 | * `python` – Name of the `python_runtime` resource to use. If not specified, the 284 | most recently declared `python_runtime` will be used. Can also be set to the 285 | full path to a `python` binary. 286 | * `setuptools_version` – Version of Setuptools to install. If set to `true`, use 287 | the latest. If set to `false`, do not install Setuptools. *(default: true)* 288 | * `system_site_packages` – Enable or disable visibilty of system packages in 289 | the environment. *(default: false)* 290 | * `user` – System user to create the virtualenv. 291 | * `wheel_version` – Version of wheel to install. If set to `true`, use the 292 | latest. If set to `false`, do not install wheel. 293 | 294 | ### `pip_requirements` 295 | 296 | The `pip_requirements` resource installs packages based on a `requirements.txt` 297 | file. 298 | 299 | ```ruby 300 | pip_requirements '/opt/myapp/requirements.txt' 301 | ``` 302 | 303 | The underlying `pip install` command will run on every converge, but 304 | notifications will only be triggered if a package is actually installed. 305 | 306 | #### Actions 307 | 308 | * `:install` – Install the requirements. *(default)* 309 | * `:upgrade` – Install using the `--upgrade` flag. 310 | 311 | #### Properties 312 | 313 | * `path` – Path to the requirements file, or a folder containing the 314 | requirements file. *(name property)* 315 | * `cwd` – Directory to run `pip` from. *(default: directory containing the 316 | `requirements.txt`)* 317 | * `group` – System group to install the packages. 318 | * `options` – Command line options for use with `pip install`. 319 | * `python` – Name of the `python_runtime` resource to use. If not specified, the 320 | most recently declared `python_runtime` will be used. Can also be set to the 321 | full path to a `python` binary. 322 | * `user` – System user to install the packages. 323 | * `virtualenv` – Name of the `python_virtualenv` resource to use. This is 324 | mutually exclusive with the `python` property. 325 | 326 | ## Python Providers 327 | 328 | ### Common Options 329 | 330 | These provider options are supported by all providers. 331 | 332 | * `pip_version` – Override the pip version. 333 | * `setuptools_version` – Override the Setuptools version. 334 | * `version` – Override the Python version. 335 | * `virtualenv_version` – Override the virtualenv version. 336 | * `wheel_version` – Override the wheel version. 337 | 338 | ### `system` 339 | 340 | The `system` provider installs Python using system packages. This is currently 341 | only tested on platforms using `apt-get` and `yum` (Debian, Ubuntu, RHEL, CentOS 342 | Amazon Linux, and Fedora) and is a default provider on those platforms. It may 343 | work on other platforms but is untested. 344 | 345 | ```ruby 346 | python_runtime 'myapp' do 347 | provider :system 348 | version '2.7' 349 | end 350 | ``` 351 | 352 | #### Options 353 | 354 | * `dev_package` – Install the package with the headers and other development 355 | files. Can be set to a string to select the dev package specifically. 356 | *(default: true)* 357 | * `package_name` – Override auto-detection of the package name. 358 | * `package_upgrade` – Install using action `:upgrade`. *(default: false)* 359 | * `package_version` – Override auto-detection of the package version. 360 | 361 | ### `scl` 362 | 363 | The `scl` provider installs Python using the [Software Collections](https://www.softwarecollections.org/) 364 | packages. This is only available on RHEL, CentOS, and Fedora. SCL offers more 365 | recent versions of Python than the system packages for the most part. If an SCL 366 | package exists for the requested version, it will be used in preference to the 367 | `system` provider. 368 | 369 | ```ruby 370 | python_runtime 'myapp' do 371 | provider :scl 372 | version '3.4' 373 | end 374 | ``` 375 | 376 | ### `portable_pypy` 377 | 378 | The `portable_pypy` provider installs Python using the [Portable PyPy](https://github.com/squeaky-pl/portable-pypy) 379 | packages. These are only available for Linux, but should work on any Linux OS. 380 | 381 | ```ruby 382 | python_runtime 'myapp' do 383 | provider :portable_pypy 384 | version 'pypy' 385 | end 386 | ``` 387 | 388 | ### `portable_pypy3` 389 | 390 | The `portable_pypy3` provider installs Python 3 using the [Portable PyPy](https://github.com/squeaky-pl/portable-pypy) 391 | packages. These are only available for Linux, but should work on any Linux OS. 392 | 393 | ```ruby 394 | python_runtime 'myapp' do 395 | provider :portable_pypy3 396 | version 'pypy3' 397 | end 398 | ``` 399 | 400 | #### Options 401 | 402 | * `folder` – Folder to install PyPy in. *(default: /opt/)* 403 | * `url` – URL to download the package from. *(default: automatic)* 404 | 405 | ### `deadsnakes` 406 | 407 | *Coming soon!* 408 | 409 | ### `python-build` 410 | 411 | *Coming soon!* 412 | 413 | ## Upgrading from the `python` Cookbook 414 | 415 | The older `python` cookbook is not directly compatible with this one, but the 416 | broad strokes overlap well. The `python::default` recipe is roughly equivalent 417 | to the `poise-python::default` recipe. The `python::pip` and `python::virtualenv` 418 | recipes are no longer needed as installing those things is now part of the 419 | `python_runtime` resource. The `python::package` recipe corresponds with the 420 | `system` provider for the `python_runtime` resource, and can generally be 421 | replaced with `poise-python::default`. At this time there is no provider to 422 | install from source so there is no replacement for the `python::source` recipe, 423 | however this is planned for the future via a `python-build` provider. 424 | 425 | The `python_pip` resource can be replaced with `python_package`, though the 426 | `environment` property has been removed. The `python_virtualenv` resource can remain 427 | unchanged except for the `interpreter` property now being `python` and the 428 | `options` property has been removed. 429 | 430 | ## Sponsors 431 | 432 | Development sponsored by [Bloomberg](http://www.bloomberg.com/company/technology/). 433 | 434 | The Poise test server infrastructure is sponsored by [Rackspace](https://rackspace.com/). 435 | 436 | ## License 437 | 438 | Copyright 2015-2017, Noah Kantrowitz 439 | 440 | Licensed under the Apache License, Version 2.0 (the "License"); 441 | you may not use this file except in compliance with the License. 442 | You may obtain a copy of the License at 443 | 444 | http://www.apache.org/licenses/LICENSE-2.0 445 | 446 | Unless required by applicable law or agreed to in writing, software 447 | distributed under the License is distributed on an "AS IS" BASIS, 448 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 449 | See the License for the specific language governing permissions and 450 | limitations under the License. 451 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | ENV['KITCHEN_CONCURRENCY'] = '10' 18 | require 'poise_boiler/rakefile' 19 | -------------------------------------------------------------------------------- /chef/attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Default inversion options. 18 | default['poise-python']['provider'] = 'auto' 19 | default['poise-python']['options'] = {} 20 | 21 | # Used for the default recipe. 22 | default['poise-python']['install_python2'] = true 23 | default['poise-python']['install_python3'] = false 24 | default['poise-python']['install_pypy'] = false 25 | -------------------------------------------------------------------------------- /chef/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Default runtimes, last one will be the default. 18 | python_runtime 'pypy' if node['poise-python']['install_pypy'] 19 | python_runtime '3' if node['poise-python']['install_python3'] 20 | python_runtime '2' if node['poise-python']['install_python2'] 21 | -------------------------------------------------------------------------------- /lib/poise_python.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | module PoisePython 19 | autoload :Error, 'poise_python/error' 20 | autoload :Resources, 'poise_python/resources' 21 | autoload :PythonCommandMixin, 'poise_python/python_command_mixin' 22 | autoload :PythonProviders, 'poise_python/python_providers' 23 | autoload :Utils, 'poise_python/utils' 24 | autoload :VERSION, 'poise_python/version' 25 | end 26 | -------------------------------------------------------------------------------- /lib/poise_python/cheftie.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_python/resources' 18 | require 'poise_python/python_providers' 19 | -------------------------------------------------------------------------------- /lib/poise_python/error.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_languages' 18 | 19 | 20 | module PoisePython 21 | class Error < PoiseLanguages::Error 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/poise_python/python_command_mixin.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise/utils' 18 | require 'poise_languages' 19 | 20 | 21 | module PoisePython 22 | # Mixin for resources and providers which run Python commands. 23 | # 24 | # @since 1.0.0 25 | module PythonCommandMixin 26 | include Poise::Utils::ResourceProviderMixin 27 | 28 | # Mixin for resources which run Python commands. 29 | module Resource 30 | include PoiseLanguages::Command::Mixin::Resource(:python) 31 | 32 | # Wrapper for setting the parent to be a virtualenv. 33 | # 34 | # @param name [String] Name of the virtualenv resource. 35 | # @return [void] 36 | def virtualenv(name) 37 | if name.is_a?(PoisePython::Resources::PythonVirtualenv::Resource) 38 | parent_python(name) 39 | else 40 | parent_python("python_virtualenv[#{name}]") 41 | end 42 | end 43 | end 44 | 45 | module Provider 46 | include PoiseLanguages::Command::Mixin::Provider(:python) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/platform/provider_priority_map' 18 | 19 | require 'poise_python/python_providers/dummy' 20 | require 'poise_python/python_providers/msi' 21 | require 'poise_python/python_providers/portable_pypy' 22 | require 'poise_python/python_providers/portable_pypy3' 23 | require 'poise_python/python_providers/scl' 24 | require 'poise_python/python_providers/system' 25 | 26 | 27 | module PoisePython 28 | # Inversion providers for the python_runtime resource. 29 | # 30 | # @since 1.0.0 31 | module PythonProviders 32 | autoload :Base, 'poise_python/python_providers/base' 33 | 34 | Chef::Platform::ProviderPriorityMap.instance.priority(:python_runtime, [ 35 | PoisePython::PythonProviders::Dummy, 36 | PoisePython::PythonProviders::Msi, 37 | PoisePython::PythonProviders::PortablePyPy3, 38 | PoisePython::PythonProviders::PortablePyPy, 39 | PoisePython::PythonProviders::Scl, 40 | PoisePython::PythonProviders::System, 41 | ]) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/provider' 18 | require 'poise' 19 | 20 | 21 | module PoisePython 22 | module PythonProviders 23 | class Base < Chef::Provider 24 | include Poise(inversion: :python_runtime) 25 | 26 | # Set default inversion options. 27 | # 28 | # @api private 29 | def self.default_inversion_options(node, new_resource) 30 | super.merge({ 31 | get_pip_url: new_resource.get_pip_url, 32 | pip_version: new_resource.pip_version, 33 | setuptools_version: new_resource.setuptools_version, 34 | version: new_resource.version, 35 | virtualenv_version: new_resource.virtualenv_version, 36 | wheel_version: new_resource.wheel_version, 37 | }) 38 | end 39 | 40 | # The `install` action for the `python_runtime` resource. 41 | # 42 | # @return [void] 43 | def action_install 44 | # First inner converge for the Python install. 45 | notifying_block do 46 | install_python 47 | end 48 | # Second inner converge for the support tools. This is needed because 49 | # we run a python command to check if venv is available. 50 | notifying_block do 51 | install_pip 52 | install_setuptools 53 | install_wheel 54 | install_virtualenv 55 | end 56 | end 57 | 58 | # The `uninstall` action for the `python_runtime` resource. 59 | # 60 | # @abstract 61 | # @return [void] 62 | def action_uninstall 63 | notifying_block do 64 | uninstall_python 65 | end 66 | end 67 | 68 | # The path to the `python` binary. This is an output property. 69 | # 70 | # @abstract 71 | # @return [String] 72 | def python_binary 73 | raise NotImplementedError 74 | end 75 | 76 | # The environment variables for this Python. This is an output property. 77 | # 78 | # @return [Hash] 79 | def python_environment 80 | {} 81 | end 82 | 83 | private 84 | 85 | # Install the Python runtime. Must be implemented by subclass. 86 | # 87 | # @abstract 88 | # @return [void] 89 | def install_python 90 | raise NotImplementedError 91 | end 92 | 93 | # Uninstall the Python runtime. Must be implemented by subclass. 94 | # 95 | # @abstract 96 | # @return [void] 97 | def uninstall_python 98 | raise NotImplementedError 99 | end 100 | 101 | # Install pip in to the Python runtime. 102 | # 103 | # @return [void] 104 | def install_pip 105 | pip_version_or_url = options[:pip_version] 106 | return unless pip_version_or_url 107 | # If there is a : in the version, use it as a URL and ignore the actual 108 | # URL option. 109 | if pip_version_or_url.is_a?(String) && pip_version_or_url.include?(':') 110 | pip_version = nil 111 | pip_url = pip_version_or_url 112 | else 113 | pip_version = pip_version_or_url 114 | pip_url = options[:get_pip_url] 115 | end 116 | Chef::Log.debug("[#{new_resource}] Installing pip #{pip_version || 'latest'}") 117 | # Install or bootstrap pip. 118 | python_runtime_pip new_resource.name do 119 | parent new_resource 120 | # If the version is `true`, don't pass it at all. 121 | version pip_version if pip_version.is_a?(String) 122 | get_pip_url pip_url 123 | end 124 | end 125 | 126 | # Install setuptools in to the Python runtime. This is very similar to the 127 | # {#install_wheel} and {#install_virtualenv} methods but they are kept 128 | # separate for the benefit of subclasses being able to override them 129 | # individually. 130 | # 131 | # @return [void] 132 | def install_setuptools 133 | # Captured because #options conflicts with Chef::Resource::Package#options. 134 | setuptools_version = options[:setuptools_version] 135 | return unless setuptools_version 136 | Chef::Log.debug("[#{new_resource}] Installing setuptools #{setuptools_version == true ? 'latest' : setuptools_version}") 137 | # Install setuptools via pip. 138 | python_package 'setuptools' do 139 | parent_python new_resource 140 | version setuptools_version if setuptools_version.is_a?(String) 141 | end 142 | end 143 | 144 | # Install wheel in to the Python runtime. 145 | # 146 | # @return [void] 147 | def install_wheel 148 | # Captured because #options conflicts with Chef::Resource::Package#options. 149 | wheel_version = options[:wheel_version] 150 | return unless wheel_version 151 | Chef::Log.debug("[#{new_resource}] Installing wheel #{wheel_version == true ? 'latest' : wheel_version}") 152 | # Install wheel via pip. 153 | python_package 'wheel' do 154 | parent_python new_resource 155 | version wheel_version if wheel_version.is_a?(String) 156 | end 157 | end 158 | 159 | # Install virtualenv in to the Python runtime. 160 | # 161 | # @return [void] 162 | def install_virtualenv 163 | # Captured because #options conflicts with Chef::Resource::Package#options. 164 | virtualenv_version = options[:virtualenv_version] 165 | return unless virtualenv_version 166 | # Check if the venv module exists. 167 | cmd = poise_shell_out([python_binary, '-m', 'venv', '-h'], environment: python_environment) 168 | return unless cmd.error? 169 | Chef::Log.debug("[#{new_resource}] Installing virtualenv #{virtualenv_version == true ? 'latest' : virtualenv_version}") 170 | # Install virtualenv via pip. 171 | python_package 'virtualenv' do 172 | parent_python new_resource 173 | version virtualenv_version if virtualenv_version.is_a?(String) 174 | end 175 | end 176 | 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/dummy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_python/python_providers/base' 18 | 19 | 20 | module PoisePython 21 | module PythonProviders 22 | # Inversion provider for the `python_runtime` resource to use a fake Python, 23 | # for use in unit tests. 24 | # 25 | # @since 1.1.0 26 | # @provides dummy 27 | class Dummy < Base 28 | provides(:dummy) 29 | 30 | # Enable by default on ChefSpec. 31 | # 32 | # @api private 33 | def self.provides_auto?(node, _resource) 34 | node.platform?('chefspec') 35 | end 36 | 37 | # Manual overrides for dummy data. 38 | # 39 | # @api private 40 | def self.default_inversion_options(node, resource) 41 | super.merge({ 42 | python_binary: ::File.join('', 'python'), 43 | python_environment: nil, 44 | }) 45 | end 46 | 47 | # The `install` action for the `python_runtime` resource. 48 | # 49 | # @return [void] 50 | def action_install 51 | # This space left intentionally blank. 52 | end 53 | 54 | # The `uninstall` action for the `python_runtime` resource. 55 | # 56 | # @return [void] 57 | def action_uninstall 58 | # This space left intentionally blank. 59 | end 60 | 61 | # Path to the non-existent python. 62 | # 63 | # @return [String] 64 | def python_binary 65 | options['python_binary'] 66 | end 67 | 68 | # Environment for the non-existent python. 69 | # 70 | # @return [String] 71 | def python_environment 72 | options['python_environment'] || super 73 | end 74 | 75 | end 76 | end 77 | end 78 | 79 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/msi.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/resource' 18 | 19 | require 'poise_python/error' 20 | require 'poise_python/python_providers/base' 21 | 22 | 23 | module PoisePython 24 | module PythonProviders 25 | class Msi < Base 26 | provides(:msi) 27 | 28 | MSI_VERSIONS = %w{3.4.4 3.3.5 3.2.5 3.1.4 3.0.1 2.7.10 2.6.5 2.5.4} 29 | 30 | def self.provides_auto?(node, resource) 31 | # Only enable by default on Windows and not for Python 3.5 because that 32 | # uses the win_binaries provider. 33 | node.platform_family?('windows') #&& resource.version != '3' && ::Gem::Requirement.create('< 3.5').satisfied_by(::Gem::Version.create(new_resource.version)) 34 | end 35 | 36 | def python_binary 37 | return options['python_binary'] if options['python_binary'] 38 | if package_version =~ /^(\d+)\.(\d+)\./ 39 | ::File.join(ENV['SystemDrive'], "Python#{$1}#{$2}", 'python.exe') 40 | else 41 | raise "Can't find Python binary for #{package_version}" 42 | end 43 | end 44 | 45 | private 46 | 47 | def install_python 48 | version = package_version 49 | windows_package 'python' do 50 | source "https://www.python.org/ftp/python/#{version}/python-#{version}#{node['machine'] == 'x86_64' ? '.amd64' : ''}.msi" 51 | end 52 | end 53 | 54 | def uninstall_python 55 | raise NotImplementedError 56 | end 57 | 58 | def package_version 59 | MSI_VERSIONS.find {|ver| ver.start_with?(new_resource.version) } || new_resource.version 60 | end 61 | 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/portable_pypy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_languages/static' 18 | 19 | require 'poise_python/error' 20 | require 'poise_python/python_providers/base' 21 | 22 | 23 | module PoisePython 24 | module PythonProviders 25 | class PortablePyPy < Base 26 | provides(:portable_pypy) 27 | include PoiseLanguages::Static( 28 | name: 'pypy', 29 | versions: %w{5.7.1 5.6 5.4.1 5.4 5.3.1 5.1.1 5.1 5.0.1 5.0 4.0.1 2.6.1 2.5.1 2.5 2.4 2.3.1 2.3 2.2.1 2.2 2.1 2.0.2}, 30 | machines: %w{linux-i686 linux-x86_64}, 31 | url: 'https://bitbucket.org/squeaky/portable-pypy/downloads/pypy-%{version}-%{kernel}_%{machine}-portable.tar.bz2' 32 | ) 33 | 34 | def python_binary 35 | ::File.join(static_folder, 'bin', 'pypy') 36 | end 37 | 38 | private 39 | 40 | def install_python 41 | install_static 42 | end 43 | 44 | def uninstall_python 45 | uninstall_static 46 | end 47 | 48 | end 49 | end 50 | end 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/portable_pypy3.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_languages/static' 18 | 19 | require 'poise_python/error' 20 | require 'poise_python/python_providers/base' 21 | 22 | 23 | module PoisePython 24 | module PythonProviders 25 | class PortablePyPy3 < Base 26 | provides(:portable_pypy3) 27 | include PoiseLanguages::Static( 28 | name: 'pypy3', 29 | # Don't put prereleases first so they aren't used for prefix matches on ''. 30 | versions: %w{2.4 5.7.1-beta 5.7-beta 5.5-alpha-20161014 5.5-alpha-20161013 5.2-alpha-20160602 2.3.1}, 31 | machines: %w{linux-i686 linux-x86_64}, 32 | url: 'https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3-%{version}-%{kernel}_%{machine}-portable.tar.bz2' 33 | ) 34 | 35 | def self.default_inversion_options(node, resource) 36 | super.tap do |options| 37 | if resource.version && resource.version =~ /^(pypy3-)?5(\.\d)?/ 38 | # We need a different default base URL for pypy3.3 39 | # This is the same as before but `/pypy3.3` as the prefix on the filename. 40 | basename = if $2 == '.2' || $2 == '.5' 41 | 'pypy3.3' 42 | else 43 | 'pypy3.5' 44 | end 45 | options['url'] = "https://bitbucket.org/squeaky/portable-pypy/downloads/#{basename}-%{version}-%{kernel}_%{machine}-portable.tar.bz2" 46 | end 47 | end 48 | end 49 | 50 | def python_binary 51 | ::File.join(static_folder, 'bin', 'pypy') 52 | end 53 | 54 | private 55 | 56 | def install_python 57 | install_static 58 | end 59 | 60 | def uninstall_python 61 | uninstall_static 62 | end 63 | 64 | end 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/scl.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/resource' 18 | require 'poise_languages' 19 | 20 | require 'poise_python/error' 21 | require 'poise_python/python_providers/base' 22 | 23 | 24 | module PoisePython 25 | module PythonProviders 26 | class Scl < Base 27 | include PoiseLanguages::Scl::Mixin 28 | provides(:scl) 29 | scl_package('3.6.3', 'rh-python36', 'rh-python36-python-devel') 30 | scl_package('3.5.1', 'rh-python35', 'rh-python35-python-devel') 31 | scl_package('3.4.2', 'rh-python34', 'rh-python34-python-devel') 32 | scl_package('3.3.2', 'python33', 'python33-python-devel') 33 | scl_package('2.7.8', 'python27', 'python27-python-devel') 34 | 35 | def python_binary 36 | ::File.join(scl_folder, 'root', 'usr', 'bin', 'python') 37 | end 38 | 39 | def python_environment 40 | scl_environment 41 | end 42 | 43 | private 44 | 45 | def install_python 46 | install_scl_package 47 | end 48 | 49 | def uninstall_python 50 | uninstall_scl_package 51 | end 52 | 53 | end 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /lib/poise_python/python_providers/system.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/resource' 18 | require 'poise_languages' 19 | 20 | require 'poise_python/error' 21 | require 'poise_python/python_providers/base' 22 | 23 | 24 | module PoisePython 25 | module PythonProviders 26 | class System < Base 27 | include PoiseLanguages::System::Mixin 28 | provides(:system) 29 | packages('python', { 30 | debian: { 31 | '~> 10.0' => %w{python3.6 python2.7}, 32 | '~> 9.0' => %w{python3.5 python2.7}, 33 | '~> 8.0' => %w{python3.4 python2.7}, 34 | '~> 7.0' => %w{python3.2 python2.7 python2.6}, 35 | '~> 6.0' => %w{python3.1 python2.6 python2.5}, 36 | }, 37 | ubuntu: { 38 | '18.04' => %w{python3.6 python2.7}, 39 | '16.04' => %w{python3.5 python2.7}, 40 | '14.04' => %w{python3.4 python2.7}, 41 | '12.04' => %w{python3.2 python2.7}, 42 | '10.04' => %w{python3.1 python2.6}, 43 | }, 44 | redhat: {default: %w{python}}, 45 | centos: {default: %w{python}}, 46 | fedora: {default: %w{python3 python}}, 47 | amazon: {default: %w{python34 python27 python26 python}}, 48 | }) 49 | 50 | # Output value for the Python binary we are installing. Seems to match 51 | # package name on all platforms I've checked. 52 | def python_binary 53 | ::File.join('', 'usr', 'bin', system_package_name) 54 | end 55 | 56 | private 57 | 58 | def install_python 59 | install_system_packages 60 | if node.platform_family?('debian') && system_package_name == 'python3.6' 61 | # Ubuntu 18.04 and Debian 10 have some weird dependency fuckery going on. 62 | _options = options 63 | package %w{python3.6-venv python3.6-distutils} do 64 | action(:upgrade) if _options['package_upgrade'] 65 | end 66 | end 67 | end 68 | 69 | def uninstall_python 70 | if node.platform_family?('debian') && system_package_name == 'python3.6' 71 | # Other side of the depdency nonsense. 72 | package %w{python3.6-venv python3.6-distutils} do 73 | action(:purge) 74 | end 75 | end 76 | uninstall_system_packages 77 | end 78 | 79 | def system_package_candidates(version) 80 | [].tap do |names| 81 | # For two (or more) digit versions. 82 | if match = version.match(/^(\d+\.\d+)/) 83 | # Debian style pythonx.y 84 | names << "python#{match[1]}" 85 | # Amazon style pythonxy 86 | names << "python#{match[1].gsub(/\./, '')}" 87 | end 88 | # Aliases for 2 and 3. 89 | if version == '3' || version == '' 90 | names.concat(%w{python3.7 python37 python3.6 python36 python3.5 python35 python3.4 python34 python3.3 python33 python3.2 python32 python3.1 python31 python3.0 python30 python3}) 91 | end 92 | if version == '2' || version == '' 93 | names.concat(%w{python2.7 python27 python2.6 python26 python2.5 python25}) 94 | end 95 | # For RHEL and friends. 96 | names << 'python' 97 | names.uniq! 98 | end 99 | end 100 | 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/poise_python/resources.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_python/resources/pip_requirements' 18 | require 'poise_python/resources/python_package' 19 | require 'poise_python/resources/python_runtime' 20 | require 'poise_python/resources/python_runtime_pip' 21 | require 'poise_python/resources/python_execute' 22 | require 'poise_python/resources/python_virtualenv' 23 | 24 | 25 | module PoisePython 26 | # Chef resources and providers for poise-python. 27 | # 28 | # @since 1.0.0 29 | module Resources 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/poise_python/resources/pip_requirements.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'shellwords' 18 | 19 | require 'chef/provider' 20 | require 'chef/resource' 21 | require 'poise' 22 | 23 | require 'poise_python/python_command_mixin' 24 | 25 | 26 | module PoisePython 27 | module Resources 28 | # (see PipRequirements::Resource) 29 | # @since 1.0.0 30 | module PipRequirements 31 | # A `pip_requirements` resource to install packages from a requirements.txt 32 | # file using pip. 33 | # 34 | # @provides pip_requirements 35 | # @action install 36 | # @action upgrade 37 | # @example 38 | # pip_requirements '/opt/myapp/requirements.txt' 39 | class Resource < Chef::Resource 40 | include PoisePython::PythonCommandMixin 41 | provides(:pip_requirements) 42 | actions(:install, :upgrade) 43 | 44 | # @!attribute path 45 | # Path to the requirements file, or a folder containing the 46 | # requirements file. 47 | # @return [String] 48 | attribute(:path, kind_of: String, name_attribute: true) 49 | # @!attribute cwd 50 | # Directory to run pip from. Defaults to the folder containing the 51 | # requirements.txt. 52 | # @return [String] 53 | attribute(:cwd, kind_of: String, default: lazy { default_cwd }) 54 | # @!attribute group 55 | # System group to install the package. 56 | # @return [String, Integer, nil] 57 | attribute(:group, kind_of: [String, Integer, NilClass]) 58 | # @!attribute options 59 | # Options string to be used with `pip install`. 60 | # @return [String, nil, false] 61 | attribute(:options, kind_of: [String, NilClass, FalseClass]) 62 | # @!attribute user 63 | # System user to install the package. 64 | # @return [String, Integer, nil] 65 | attribute(:user, kind_of: [String, Integer, NilClass]) 66 | 67 | private 68 | 69 | # Default value for the {#cwd} property. 70 | # 71 | # @return [String] 72 | def default_cwd 73 | if ::File.directory?(path) 74 | path 75 | else 76 | ::File.dirname(path) 77 | end 78 | end 79 | end 80 | 81 | # The default provider for `pip_requirements`. 82 | # 83 | # @see Resource 84 | # @provides pip_requirements 85 | class Provider < Chef::Provider 86 | include Poise 87 | include PoisePython::PythonCommandMixin 88 | provides(:pip_requirements) 89 | 90 | # The `install` action for the `pip_requirements` resource. 91 | # 92 | # @return [void] 93 | def action_install 94 | install_requirements(upgrade: false) 95 | end 96 | 97 | # The `upgrade` action for the `pip_requirements` resource. 98 | # 99 | # @return [void] 100 | def action_upgrade 101 | install_requirements(upgrade: true) 102 | end 103 | 104 | private 105 | 106 | # Run an install --requirements command and parse the output. 107 | # 108 | # @param upgrade [Boolean] If we should use the --upgrade flag. 109 | # @return [void] 110 | def install_requirements(upgrade: false) 111 | if new_resource.options 112 | # Use a string because we have some options. 113 | cmd = '-m pip.__main__ install' 114 | cmd << ' --upgrade' if upgrade 115 | cmd << " #{new_resource.options}" 116 | cmd << " --requirement #{Shellwords.escape(requirements_path)}" 117 | else 118 | # No options, use an array to be slightly faster. 119 | cmd = %w{-m pip.__main__ install} 120 | cmd << '--upgrade' if upgrade 121 | cmd << '--requirement' 122 | cmd << requirements_path 123 | end 124 | output = python_shell_out!(cmd, user: new_resource.user, group: new_resource.group, cwd: new_resource.cwd).stdout 125 | if output.include?('Successfully installed') 126 | new_resource.updated_by_last_action(true) 127 | end 128 | end 129 | 130 | # Find the true path to the requirements file. 131 | # 132 | # @return [String] 133 | def requirements_path 134 | if ::File.directory?(new_resource.path) 135 | ::File.join(new_resource.path, 'requirements.txt') 136 | else 137 | new_resource.path 138 | end 139 | end 140 | 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_execute.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/mixin/which' 18 | require 'chef/provider/execute' 19 | require 'chef/resource/execute' 20 | require 'poise' 21 | 22 | require 'poise_python/python_command_mixin' 23 | 24 | 25 | module PoisePython 26 | module Resources 27 | # (see PythonExecute::Resource) 28 | # @since 1.0.0 29 | module PythonExecute 30 | # A `python_execute` resource to run Python scripts and commands. 31 | # 32 | # @provides python_execute 33 | # @action run 34 | # @example 35 | # python_execute 'myapp.py' do 36 | # user 'myuser' 37 | # end 38 | class Resource < Chef::Resource::Execute 39 | include PoisePython::PythonCommandMixin 40 | provides(:python_execute) 41 | actions(:run) 42 | end 43 | 44 | # The default provider for `python_execute`. 45 | # 46 | # @see Resource 47 | # @provides python_execute 48 | class Provider < Chef::Provider::Execute 49 | include Chef::Mixin::Which 50 | provides(:python_execute) 51 | 52 | private 53 | 54 | # Command to pass to shell_out. 55 | # 56 | # @return [String, Array] 57 | def command 58 | if new_resource.command.is_a?(Array) 59 | [new_resource.python] + new_resource.command 60 | else 61 | "#{new_resource.python} #{new_resource.command}" 62 | end 63 | end 64 | 65 | # Environment variables to pass to shell_out. 66 | # 67 | # @return [Hash] 68 | def environment 69 | if new_resource.parent_python 70 | environment = new_resource.parent_python.python_environment 71 | if new_resource.environment 72 | environment = environment.merge(new_resource.environment) 73 | end 74 | environment 75 | else 76 | new_resource.environment 77 | end 78 | end 79 | 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_package.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'shellwords' 18 | 19 | require 'chef/mixin/which' 20 | require 'chef/provider/package' 21 | require 'chef/resource/package' 22 | require 'poise' 23 | 24 | require 'poise_python/python_command_mixin' 25 | 26 | 27 | module PoisePython 28 | module Resources 29 | # (see PythonPackage::Resource) 30 | # @since 1.0.0 31 | module PythonPackage 32 | # A Python snippet to check which versions of things pip would try to 33 | # install. Probably not 100% bulletproof. 34 | PIP_HACK_SCRIPT = <<-EOH 35 | import json 36 | import re 37 | import sys 38 | 39 | import pip 40 | # Don't use pkg_resources because I don't want to require it before this anyway. 41 | if re.match(r'0\\.|1\\.|6\\.0', pip.__version__): 42 | sys.stderr.write('The python_package resource requires pip >= 6.1.0, currently '+pip.__version__+'\\n') 43 | sys.exit(1) 44 | 45 | try: 46 | from pip.commands import InstallCommand 47 | from pip.index import PackageFinder 48 | from pip.req import InstallRequirement 49 | install_req_from_line = InstallRequirement.from_line 50 | except ImportError: 51 | # Pip 10 moved all internals to their own package. 52 | from pip._internal.commands import InstallCommand 53 | from pip._internal.index import PackageFinder 54 | try: 55 | # Pip 18.1 moved from_line to the constructors 56 | from pip._internal.req.constructors import install_req_from_line 57 | except ImportError: 58 | from pip._internal.req import InstallRequirement 59 | install_req_from_line = InstallRequirement.from_line 60 | 61 | packages = {} 62 | cmd = InstallCommand() 63 | options, args = cmd.parse_args(sys.argv[1:]) 64 | with cmd._build_session(options) as session: 65 | if options.no_index: 66 | index_urls = [] 67 | else: 68 | index_urls = [options.index_url] + options.extra_index_urls 69 | finder_options = dict( 70 | find_links=options.find_links, 71 | index_urls=index_urls, 72 | allow_all_prereleases=options.pre, 73 | process_dependency_links=options.process_dependency_links, 74 | trusted_hosts=options.trusted_hosts, 75 | session=session, 76 | ) 77 | if getattr(options, 'format_control', None): 78 | finder_options['format_control'] = options.format_control 79 | finder = PackageFinder(**finder_options) 80 | find_all = getattr(finder, 'find_all_candidates', getattr(finder, '_find_all_versions', None)) 81 | for arg in args: 82 | req = install_req_from_line(arg) 83 | found = finder.find_requirement(req, True) 84 | all_candidates = find_all(req.name) 85 | candidate = [c for c in all_candidates if c.location == found] 86 | if candidate: 87 | packages[candidate[0].project.lower()] = str(candidate[0].version) 88 | json.dump(packages, sys.stdout) 89 | EOH 90 | 91 | # A `python_package` resource to manage Python installations using pip. 92 | # 93 | # @provides python_package 94 | # @action install 95 | # @action upgrade 96 | # @action uninstall 97 | # @example 98 | # python_package 'django' do 99 | # python '2' 100 | # version '1.8.3' 101 | # end 102 | class Resource < Chef::Resource::Package 103 | include PoisePython::PythonCommandMixin 104 | provides(:python_package) 105 | # Manually create matchers because #actions is unreliable. 106 | %i{install upgrade remove}.each do |action| 107 | Poise::Helpers::ChefspecMatchers.create_matcher(:python_package, action) 108 | end 109 | 110 | # @!attribute group 111 | # System group to install the package. 112 | # @return [String, Integer, nil] 113 | attribute(:group, kind_of: [String, Integer, NilClass], default: lazy { default_group }) 114 | # @!attribute install_options 115 | # Options string to be used with `pip install`. 116 | # @return [String, Array, nil, false] 117 | attribute(:install_options, kind_of: [String, Array, NilClass, FalseClass], default: nil) 118 | # @!attribute list_options 119 | # Options string to be used with `pip list`. 120 | # @return [String, Array, nil, false] 121 | attribute(:list_options, kind_of: [String, Array, NilClass, FalseClass], default: nil) 122 | # @!attribute user 123 | # System user to install the package. 124 | # @return [String, Integer, nil] 125 | attribute(:user, kind_of: [String, Integer, NilClass], default: lazy { default_user }) 126 | 127 | # This should probably be in the base class but ¯\_(ツ)_/¯. 128 | # @!attribute allow_downgrade 129 | # Allow downgrading the package. 130 | # @return [Boolean] 131 | attribute(:allow_downgrade, kind_of: [TrueClass, FalseClass], default: false) 132 | 133 | def initialize(*args) 134 | super 135 | # For older Chef. 136 | @resource_name = :python_package 137 | # We don't have these actions. 138 | @allowed_actions.delete(:purge) 139 | @allowed_actions.delete(:reconfig) 140 | end 141 | 142 | # Upstream attribute we don't support. Sets are an error and gets always 143 | # return nil. 144 | # 145 | # @api private 146 | # @param arg [Object] Ignored 147 | # @return [nil] 148 | def response_file(arg=nil) 149 | raise NoMethodError if arg 150 | end 151 | 152 | # (see #response_file) 153 | def response_file_variables(arg=nil) 154 | raise NoMethodError if arg && arg != {} 155 | end 156 | 157 | # (see #response_file) 158 | def source(arg=nil) 159 | raise NoMethodError if arg 160 | end 161 | 162 | private 163 | 164 | # Find a default group, if any, from the parent Python. 165 | # 166 | # @api private 167 | # @return [String, Integer, nil] 168 | def default_group 169 | # Use an explicit is_a? hack because python_runtime is a container so 170 | # it gets the whole DSL and this will always respond_to?(:group). 171 | if parent_python && parent_python.is_a?(PoisePython::Resources::PythonVirtualenv::Resource) 172 | parent_python.group 173 | else 174 | nil 175 | end 176 | end 177 | 178 | # Find a default user, if any, from the parent Python. 179 | # 180 | # @api private 181 | # @return [String, Integer, nil] 182 | def default_user 183 | # See default_group for explanation of is_a? hack grossness. 184 | if parent_python && parent_python.is_a?(PoisePython::Resources::PythonVirtualenv::Resource) 185 | parent_python.user 186 | else 187 | nil 188 | end 189 | end 190 | end 191 | 192 | # The default provider for the `python_package` resource. 193 | # 194 | # @see Resource 195 | class Provider < Chef::Provider::Package 196 | include PoisePython::PythonCommandMixin 197 | provides(:python_package) 198 | 199 | # Load current and candidate versions for all needed packages. 200 | # 201 | # @api private 202 | # @return [Chef::Resource] 203 | def load_current_resource 204 | @current_resource = new_resource.class.new(new_resource.name, run_context) 205 | current_resource.package_name(new_resource.package_name) 206 | check_package_versions(current_resource) 207 | Chef::Log.debug("[#{new_resource}] Current version: #{current_resource.version}, candidate version: #{@candidate_version}") 208 | current_resource 209 | end 210 | 211 | # Populate current and candidate versions for all needed packages. 212 | # 213 | # @api private 214 | # @param resource [PoisePython::Resources::PythonPackage::Resource] 215 | # Resource to load for. 216 | # @param version [String, Array] Current version(s) of package(s). 217 | # @return [void] 218 | def check_package_versions(resource, version=new_resource.version) 219 | version_data = Hash.new {|hash, key| hash[key] = {current: nil, candidate: nil} } 220 | # Get the version for everything currently installed. 221 | list = pip_command('list', :list, [], environment: {'PIP_FORMAT' => 'json'}).stdout 222 | parse_pip_list(list).each do |name, current| 223 | # Merge current versions in to the data. 224 | version_data[name][:current] = current 225 | end 226 | # Check for newer candidates. 227 | outdated = pip_outdated(pip_requirements(resource.package_name, version, parse: true)).stdout 228 | Chef::JSONCompat.parse(outdated).each do |name, candidate| 229 | # Merge candidates in to the existing versions. 230 | version_data[name][:candidate] = candidate 231 | end 232 | # Populate the current resource and candidate versions. Youch this is 233 | # a gross mix of data flow. 234 | if(resource.package_name.is_a?(Array)) 235 | @candidate_version = [] 236 | versions = [] 237 | [resource.package_name].flatten.each do |name| 238 | ver = version_data[parse_package_name(name)] 239 | versions << ver[:current] 240 | @candidate_version << ver[:candidate] 241 | end 242 | resource.version(versions) 243 | else 244 | ver = version_data[parse_package_name(resource.package_name)] 245 | resource.version(ver[:current]) 246 | @candidate_version = ver[:candidate] 247 | end 248 | end 249 | 250 | # Install package(s) using pip. 251 | # 252 | # @param name [String, Array] Name(s) of package(s). 253 | # @param version [String, Array] Version(s) of package(s). 254 | # @return [void] 255 | def install_package(name, version) 256 | pip_install(name, version, upgrade: false) 257 | end 258 | 259 | # Upgrade package(s) using pip. 260 | # 261 | # @param name [String, Array] Name(s) of package(s). 262 | # @param version [String, Array] Version(s) of package(s). 263 | # @return [void] 264 | def upgrade_package(name, version) 265 | pip_install(name, version, upgrade: true) 266 | end 267 | 268 | # Uninstall package(s) using pip. 269 | # 270 | # @param name [String, Array] Name(s) of package(s). 271 | # @param version [String, Array] Version(s) of package(s). 272 | # @return [void] 273 | def remove_package(name, version) 274 | pip_command('uninstall', :install, %w{--yes} + [name].flatten) 275 | end 276 | 277 | private 278 | 279 | # Convert name(s) and version(s) to an array of pkg_resources.Requirement 280 | # compatible strings. These are strings like "django" or "django==1.0". 281 | # 282 | # @param name [String, Array] Name or names for the packages. 283 | # @param version [String, Array] Version or versions for the 284 | # packages. 285 | # @param parse [Boolean] Use parsed package names. 286 | # @return [Array] 287 | def pip_requirements(name, version, parse: false) 288 | [name].flatten.zip([version].flatten).map do |n, v| 289 | n = parse_package_name(n) if parse 290 | v = v.to_s.strip 291 | if n =~ /:\/\// 292 | # Probably a URI. 293 | n 294 | elsif v.empty? 295 | # No version requirement, send through unmodified. 296 | n 297 | elsif v =~ /^\d/ 298 | "#{n}==#{v}" 299 | else 300 | # If the first character isn't a digit, assume something fancy. 301 | n + v 302 | end 303 | end 304 | end 305 | 306 | # Run a pip command. 307 | # 308 | # @param pip_command [String, nil] The pip subcommand to run (eg. install). 309 | # @param options_type [Symbol] Either `:install` to `:list` to select 310 | # which extra options to use. 311 | # @param pip_options [Array] Options for the pip command. 312 | # @param opts [Hash] Mixlib::ShellOut options. 313 | # @return [Mixlib::ShellOut] 314 | def pip_command(pip_command, options_type, pip_options=[], opts={}) 315 | runner = opts.delete(:pip_runner) || %w{-m pip.__main__} 316 | type_specific_options = new_resource.send(:"#{options_type}_options") 317 | full_cmd = if new_resource.options || type_specific_options 318 | if (new_resource.options && new_resource.options.is_a?(String)) || (type_specific_options && type_specific_options.is_a?(String)) 319 | # We have to use a string for this case to be safe because the 320 | # options are a string and I don't want to try and parse that. 321 | global_options = new_resource.options.is_a?(Array) ? Shellwords.join(new_resource.options) : new_resource.options.to_s 322 | type_specific_options = type_specific_options.is_a?(Array) ? Shellwords.join(type_specific_options) : type_specific_options.to_s 323 | "#{runner.join(' ')} #{pip_command} #{global_options} #{type_specific_options} #{Shellwords.join(pip_options)}" 324 | else 325 | runner + (pip_command ? [pip_command] : []) + (new_resource.options || []) + (type_specific_options || []) + pip_options 326 | end 327 | else 328 | # No special options, use an array to skip the extra /bin/sh. 329 | runner + (pip_command ? [pip_command] : []) + pip_options 330 | end 331 | # Set user and group. 332 | opts[:user] = new_resource.user if new_resource.user 333 | opts[:group] = new_resource.group if new_resource.group 334 | 335 | python_shell_out!(full_cmd, opts) 336 | end 337 | 338 | # Run `pip install` to install a package(s). 339 | # 340 | # @param name [String, Array] Name(s) of package(s) to install. 341 | # @param version [String, Array] Version(s) of package(s) to 342 | # install. 343 | # @param upgrade [Boolean] Use upgrade mode? 344 | # @return [Mixlib::ShellOut] 345 | def pip_install(name, version, upgrade: false) 346 | cmd = pip_requirements(name, version) 347 | # Prepend --upgrade if needed. 348 | cmd = %w{--upgrade} + cmd if upgrade 349 | pip_command('install', :install, cmd) 350 | end 351 | 352 | # Run my hacked version of `pip list --outdated` with a specific set of 353 | # package requirements. 354 | # 355 | # @see #pip_requirements 356 | # @param requirements [Array] Pip-formatted package requirements. 357 | # @return [Mixlib::ShellOut] 358 | def pip_outdated(requirements) 359 | pip_command(nil, :list, requirements, input: PIP_HACK_SCRIPT, pip_runner: %w{-}) 360 | end 361 | 362 | # Parse the output from `pip list`. Returns a hash of package key to 363 | # current version. 364 | # 365 | # @param text [String] Output to parse. 366 | # @return [Hash] 367 | def parse_pip_list(text) 368 | if text[0] == '[' 369 | # Pip 9 or newer, so it understood $PIP_FORMAT=json. 370 | Chef::JSONCompat.parse(text).each_with_object({}) do |data, memo| 371 | memo[parse_package_name(data['name'])] = data['version'] 372 | end 373 | else 374 | # Pip 8 or earlier, which doesn't support JSON output. 375 | text.split(/\r?\n/).each_with_object({}) do |line, memo| 376 | # Example of a line: 377 | # boto (2.25.0) 378 | if md = line.match(/^(\S+)\s+\(([^\s,]+).*\)$/i) 379 | memo[parse_package_name(md[1])] = md[2] 380 | else 381 | Chef::Log.debug("[#{new_resource}] Unparsable line in pip list: #{line}") 382 | end 383 | end 384 | end 385 | end 386 | 387 | # Regexp for package URLs. 388 | PACKAGE_NAME_URL = /:\/\/.*?#egg=(.*)$/ 389 | 390 | # Regexp for extras. 391 | PACKAGE_NAME_EXTRA = /^(.*?)\[.*?\]$/ 392 | 393 | # Find the underlying name from a pip input sequence. 394 | # 395 | # @param raw_name [String] Raw package name. 396 | # @return [String] 397 | def parse_package_name(raw_name) 398 | case raw_name 399 | when PACKAGE_NAME_URL, PACKAGE_NAME_EXTRA 400 | $1 401 | else 402 | raw_name 403 | end.downcase.gsub(/_/, '-') 404 | end 405 | 406 | end 407 | end 408 | end 409 | end 410 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_runtime.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/resource' 18 | require 'poise' 19 | 20 | 21 | module PoisePython 22 | module Resources 23 | # (see PythonRuntime::Resource) 24 | # @since 1.0.0 25 | module PythonRuntime 26 | # A `python_runtime` resource to manage Python installations. 27 | # 28 | # @provides python_runtime 29 | # @action install 30 | # @action uninstall 31 | # @example 32 | # python_runtime '2.7' 33 | class Resource < Chef::Resource 34 | include Poise(inversion: true, container: true) 35 | provides(:python_runtime) 36 | actions(:install, :uninstall) 37 | 38 | # @!attribute version 39 | # Version of Python to install. The version is prefix-matched so `'2'` 40 | # will install the most recent Python 2.x, and so on. 41 | # @return [String] 42 | # @example Install any version 43 | # python_runtime 'any' do 44 | # version '' 45 | # end 46 | # @example Install Python 2.7 47 | # python_runtime '2.7' 48 | attribute(:version, kind_of: String, name_attribute: true) 49 | # @!attribute get_pip_url 50 | # URL to download the get-pip.py script from. If not sure, the default 51 | # of https://bootstrap.pypa.io/get-pip.py is used. If you want to skip 52 | # the pip installer entirely, set {#pip_version} to `false`. 53 | # @return [String] 54 | # If this default value changes, fix ths 2.6-compat logic in python_runtime_pip. 55 | attribute(:get_pip_url, kind_of: String, default: 'https://bootstrap.pypa.io/get-pip.py') 56 | # @!attribute pip_version 57 | # Version of pip to install. If set to `true`, the latest available 58 | # pip will be used. If set to `false`, pip will not be installed. If 59 | # set to a URL, that will be used as the URL to get-pip.py instead of 60 | # {#get_pip_url}. 61 | # @note Disabling the pip install may result in other resources being 62 | # non-functional. 63 | # @return [String, Boolean] 64 | attribute(:pip_version, kind_of: [String, TrueClass, FalseClass], default: true) 65 | # @!attribute setuptools_version 66 | # Version of Setuptools to install. It set to `true`, the latest 67 | # available version will be used. If set to `false`, setuptools will 68 | # not be installed. 69 | # @return [String, Boolean] 70 | attribute(:setuptools_version, kind_of: [String, TrueClass, FalseClass], default: true) 71 | # @!attribute virtualenv_version 72 | # Version of Virtualenv to install. It set to `true`, the latest 73 | # available version will be used. If set to `false`, virtualenv will 74 | # not be installed. Virtualenv will never be installed if the built-in 75 | # venv module is available. 76 | # @note Disabling the virtualenv install may result in other resources 77 | # being non-functional. 78 | # @return [String, Boolean] 79 | attribute(:virtualenv_version, kind_of: [String, TrueClass, FalseClass], default: true) 80 | # @!attribute wheel_version 81 | # Version of Wheel to install. It set to `true`, the latest 82 | # available version will be used. If set to `false`, wheel will not 83 | # be installed. 84 | # @return [String, Boolean] 85 | attribute(:wheel_version, kind_of: [String, TrueClass, FalseClass], default: true) 86 | 87 | # The path to the `python` binary for this Python installation. This is 88 | # an output property. 89 | # 90 | # @return [String] 91 | # @example 92 | # execute "#{resources('python_runtime[2.7]').python_binary} myapp.py" 93 | def python_binary 94 | provider_for_action(:python_binary).python_binary 95 | end 96 | 97 | # The environment variables for this Python installation. This is an 98 | # output property. 99 | # 100 | # @return [Hash] 101 | # @example 102 | # execute '/opt/myapp.py' do 103 | # environment resources('python_runtime[2.7]').python_environment 104 | # end 105 | def python_environment 106 | provider_for_action(:python_environment).python_environment 107 | end 108 | end 109 | 110 | # Providers can be found under lib/poise_python/python_providers/ 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_runtime_pip.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'fileutils' 18 | require 'tempfile' 19 | 20 | require 'chef/resource' 21 | require 'poise' 22 | 23 | 24 | module PoisePython 25 | module Resources 26 | # (see PythonRuntimePip::Resource) 27 | # @since 1.0.0 28 | # @api private 29 | module PythonRuntimePip 30 | # Earliest version of pip we will try upgrading in-place. 31 | PIP_INPLACE_VERSION = Gem::Version.create('7.0.0') 32 | 33 | # Version to trigger the automatic get-pip.py URL fixup on for 2.6 compat. 34 | PY26_FIXUP_VERSION = Gem::Version.create('2.7') 35 | 36 | # Replacement URL for 2.6 compat. 37 | PY26_FIXUP_GETPIP_URL = 'https://bootstrap.pypa.io/2.6/get-pip.py' 38 | 39 | # A `python_runtime_pip` resource to install/upgrade pip itself. This is 40 | # used internally by `python_runtime` and is not intended to be a public 41 | # API. 42 | # 43 | # @provides python_runtime_pip 44 | # @action install 45 | # @action uninstall 46 | class Resource < Chef::Resource 47 | include Poise(parent: :python_runtime) 48 | provides(:python_runtime_pip) 49 | actions(:install, :uninstall) 50 | 51 | # @!attribute version 52 | # Version of pip to install. Only kind of works due to 53 | # https://github.com/pypa/pip/issues/1087. 54 | # @return [String] 55 | attribute(:version, kind_of: String) 56 | # @!attribute get_pip_url 57 | # URL to the get-pip.py script. 58 | # @return [String] 59 | attribute(:get_pip_url, kind_of: String, required: true) 60 | end 61 | 62 | # The default provider for `python_runtime_pip`. 63 | # 64 | # @see Resource 65 | # @provides python_runtime_pip 66 | class Provider < Chef::Provider 67 | include Poise 68 | provides(:python_runtime_pip) 69 | 70 | # @api private 71 | def load_current_resource 72 | super.tap do |current_resource| 73 | # Try to find the current version if possible. 74 | current_resource.version(pip_version) 75 | end 76 | end 77 | 78 | # The `install` action for the `python_runtime_pip` resource. 79 | # 80 | # @return [void] 81 | def action_install 82 | Chef::Log.debug("[#{new_resource}] Installing pip #{new_resource.version || 'latest'}, currently #{current_resource.version || 'not installed'}") 83 | if new_resource.version && current_resource.version == new_resource.version 84 | Chef::Log.debug("[#{new_resource}] Pip #{current_resource.version} is already at requested version") 85 | return # Desired version is installed, even if ancient. 86 | # If you have older than 7.0.0, we're re-bootstraping because lolno. 87 | elsif current_resource.version && Gem::Version.create(current_resource.version) >= PIP_INPLACE_VERSION 88 | install_pip 89 | else 90 | bootstrap_pip 91 | end 92 | end 93 | 94 | # The `uninstall` action for the `python_runtime_pip` resource. 95 | # 96 | # @return [void] 97 | def action_uninstall 98 | notifying_block do 99 | python_package 'pip' do 100 | action :uninstall 101 | parent_python new_resource.parent 102 | end 103 | end 104 | end 105 | 106 | private 107 | 108 | # Bootstrap pip using get-pip.py. 109 | # 110 | # @return [void] 111 | def bootstrap_pip 112 | # If we're on Python 2.6 and using the default get_pip_url, we need to 113 | # switch to a 2.6-compatible version. This kind of sucks because it 114 | # makes the default a magic value but it seems like the safest option. 115 | get_pip_url = new_resource.get_pip_url 116 | if get_pip_url == 'https://bootstrap.pypa.io/get-pip.py' 117 | python_version_cmd = poise_shell_out!([new_resource.parent.python_binary, '--version'], environment: new_resource.parent.python_environment) 118 | # Python 2 puts the output on stderr, 3 is on stdout. You can't make this shit up. 119 | python_version = (python_version_cmd.stdout + python_version_cmd.stderr)[/Python (\S+)/, 1] 120 | Chef::Log.debug("[#{new_resource}] Checking for Python 2.6 fixup of get-pip URL, found Python version #{python_version || '(unknown)'}") 121 | if python_version && Gem::Version.create(python_version) < PY26_FIXUP_VERSION 122 | Chef::Log.debug("[#{new_resource}] Detected old Python, enabling fixup") 123 | get_pip_url = PY26_FIXUP_GETPIP_URL 124 | end 125 | end 126 | 127 | # Always updated if we have hit this point. 128 | converge_by("Bootstrapping pip #{new_resource.version || 'latest'} from #{get_pip_url}") do 129 | # Use a temp file to hold the installer. 130 | # Put `Tempfile.create` back when Chef on Windows has a newer Ruby. 131 | # Tempfile.create(['get-pip', '.py']) do |temp| 132 | temp = Tempfile.new(['get-pip', '.py']) 133 | begin 134 | # Download the get-pip.py. 135 | get_pip = Chef::HTTP.new(get_pip_url).get('') 136 | # Write it to the temp file. 137 | temp.write(get_pip) 138 | # Close the file to flush it. 139 | temp.close 140 | # Run the install. This probably needs some handling for proxies et 141 | # al. Disable setuptools and wheel as we will install those later. 142 | # Use the environment vars instead of CLI arguments so I don't have 143 | # to deal with bootstrap versions that don't support --no-wheel. 144 | boostrap_cmd = [new_resource.parent.python_binary, temp.path, '--upgrade', '--force-reinstall'] 145 | boostrap_cmd << "pip==#{new_resource.version}" if new_resource.version 146 | Chef::Log.debug("[#{new_resource}] Running pip bootstrap command: #{boostrap_cmd.join(' ')}") 147 | # Gross is_a? hacks but because python_runtime is a container, it 148 | # gets the full DSL and this has user and group methods from that. 149 | user = new_resource.parent.is_a?(PoisePython::Resources::PythonVirtualenv::Resource) ? new_resource.parent.user : nil 150 | group = new_resource.parent.is_a?(PoisePython::Resources::PythonVirtualenv::Resource) ? new_resource.parent.group : nil 151 | FileUtils.chown(user, group, temp.path) if user || group 152 | poise_shell_out!(boostrap_cmd, environment: new_resource.parent.python_environment.merge('PIP_NO_SETUPTOOLS' => '1', 'PIP_NO_WHEEL' => '1'), group: group, user: user) 153 | ensure 154 | temp.close unless temp.closed? 155 | temp.unlink 156 | end 157 | new_pip_version = pip_version 158 | if new_resource.version && new_pip_version != new_resource.version 159 | # We probably want to downgrade, which is silly but ¯\_(ツ)_/¯. 160 | # Can be removed once https://github.com/pypa/pip/issues/1087 is fixed. 161 | # That issue is fixed, leaving a bit longer for older vendored scripts. 162 | Chef::Log.debug("[#{new_resource}] Pip bootstrap installed #{new_pip_version}, trying to install again for #{new_resource.version}") 163 | current_resource.version(new_pip_version) 164 | install_pip 165 | end 166 | end 167 | end 168 | 169 | # Upgrade (or downgrade) pip using itself. Should work back at least 170 | # pip 1.5. 171 | # 172 | # @return [void] 173 | def install_pip 174 | if new_resource.version 175 | # Already up to date, we're done here. 176 | return if current_resource.version == new_resource.version 177 | else 178 | # We don't wany a specific version, so just make a general check. 179 | return if current_resource.version 180 | end 181 | 182 | Chef::Log.debug("[#{new_resource}] Installing pip #{new_resource.version} via itself") 183 | notifying_block do 184 | # Use pip to upgrade (or downgrade) itself. 185 | python_package 'pip' do 186 | action :upgrade 187 | parent_python new_resource.parent 188 | version new_resource.version if new_resource.version 189 | allow_downgrade true 190 | end 191 | end 192 | end 193 | 194 | # Find the version of pip currently installed in this Python runtime. 195 | # Returns nil if not installed. 196 | # 197 | # @return [String, nil] 198 | def pip_version 199 | version_cmd = [new_resource.parent.python_binary, '-m', 'pip.__main__', '--version'] 200 | Chef::Log.debug("[#{new_resource}] Running pip version command: #{version_cmd.join(' ')}") 201 | cmd = poise_shell_out(version_cmd, environment: new_resource.parent.python_environment) 202 | if cmd.error? 203 | # Not installed, probably. 204 | nil 205 | else 206 | cmd.stdout[/pip ([\d.a-z]+)/, 1] 207 | end 208 | end 209 | 210 | end 211 | end 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_runtime_test.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'chef/provider' 18 | require 'chef/resource' 19 | require 'poise' 20 | 21 | 22 | module PoisePython 23 | module Resources 24 | # (see PythonRuntimeTest::Resource) 25 | # @since 1.0.0 26 | # @api private 27 | module PythonRuntimeTest 28 | # A `python_runtime_test` resource for integration testing of this 29 | # cookbook. This is an internal API and can change at any time. 30 | # 31 | # @provides python_runtime_test 32 | # @action run 33 | class Resource < Chef::Resource 34 | include Poise 35 | provides(:python_runtime_test) 36 | actions(:run) 37 | 38 | attribute(:version, kind_of: String, name_attribute: true) 39 | attribute(:runtime_provider, kind_of: Symbol) 40 | attribute(:path, kind_of: String, default: lazy { default_path }) 41 | 42 | def default_path 43 | ::File.join('', "python_test_#{name}") 44 | end 45 | end 46 | 47 | # The default provider for `python_runtime_test`. 48 | # 49 | # @see Resource 50 | # @provides python_runtime_test 51 | class Provider < Chef::Provider 52 | include Poise 53 | provides(:python_runtime_test) 54 | 55 | # The `run` action for the `python_runtime_test` resource. 56 | # 57 | # @return [void] 58 | def action_run 59 | notifying_block do 60 | # Top level directory for this test. 61 | directory new_resource.path do 62 | mode '777' 63 | end 64 | 65 | # Install and log the version. 66 | python_runtime new_resource.name do 67 | provider new_resource.runtime_provider if new_resource.runtime_provider 68 | version new_resource.version 69 | end 70 | test_version 71 | 72 | # Test python_package. 73 | python_package 'argparse' do 74 | # Needed for sqlparse but not in the stdlib until 2.7. 75 | python new_resource.name 76 | end 77 | python_package 'sqlparse remove before' do 78 | action :remove 79 | package_name 'sqlparse' 80 | python new_resource.name 81 | end 82 | test_import('sqlparse', 'sqlparse_before') 83 | python_package 'sqlparse' do 84 | python new_resource.name 85 | notifies :create, sentinel_file('sqlparse'), :immediately 86 | end 87 | test_import('sqlparse', 'sqlparse_mid') 88 | python_package 'sqlparse again' do 89 | package_name 'sqlparse' 90 | python new_resource.name 91 | notifies :create, sentinel_file('sqlparse2'), :immediately 92 | end 93 | python_package 'sqlparse remove after' do 94 | action :remove 95 | package_name 'sqlparse' 96 | python new_resource.name 97 | end 98 | test_import('sqlparse', 'sqlparse_after') 99 | 100 | # Use setuptools to test something that should always be installed. 101 | python_package 'setuptools' do 102 | python new_resource.name 103 | notifies :create, sentinel_file('setuptools'), :immediately 104 | end 105 | 106 | # Multi-package install. 107 | python_package ['pep8', 'pytz'] do 108 | python new_resource.name 109 | end 110 | test_import('pep8') 111 | test_import('pytz') 112 | 113 | # Create a virtualenv. 114 | python_virtualenv ::File.join(new_resource.path, 'venv') do 115 | python new_resource.name 116 | end 117 | 118 | # Install a package inside a virtualenv. 119 | python_package 'Pytest' do 120 | virtualenv ::File.join(new_resource.path, 'venv') 121 | end 122 | test_import('pytest') 123 | test_import('pytest', 'pytest_venv', python: nil, virtualenv: ::File.join(new_resource.path, 'venv')) 124 | 125 | # Create and install a requirements file. 126 | # Running this in a venv because of pip 8.0 and Ubuntu packaing 127 | # both requests and six. 128 | python_virtualenv ::File.join(new_resource.path, 'venv2') do 129 | python new_resource.name 130 | end 131 | file ::File.join(new_resource.path, 'requirements.txt') do 132 | content <<-EOH 133 | requests==2.7.0 134 | six==1.8.0 135 | EOH 136 | end 137 | pip_requirements ::File.join(new_resource.path, 'requirements.txt') do 138 | virtualenv ::File.join(new_resource.path, 'venv2') 139 | end 140 | test_import('requests', python: nil, virtualenv: ::File.join(new_resource.path, 'venv2')) 141 | test_import('six', python: nil, virtualenv: ::File.join(new_resource.path, 'venv2')) 142 | 143 | # Install a non-latest version of a package. 144 | python_virtualenv ::File.join(new_resource.path, 'venv3') do 145 | python new_resource.name 146 | end 147 | python_package 'requests' do 148 | version '2.8.0' 149 | virtualenv ::File.join(new_resource.path, 'venv3') 150 | end 151 | test_import('requests', 'requests_version', python: nil, virtualenv: ::File.join(new_resource.path, 'venv3')) 152 | 153 | # Don't run the user tests on Windows. 154 | unless node.platform_family?('windows') 155 | # Create a non-root user and test installing with it. 156 | test_user = "py#{new_resource.name}" 157 | test_home = ::File.join('', 'home', test_user) 158 | group 'g'+test_user do 159 | system true 160 | end 161 | user test_user do 162 | comment "Test user for python_runtime_test #{new_resource.name}" 163 | gid 'g'+test_user 164 | home test_home 165 | shell '/bin/false' 166 | system true 167 | end 168 | directory test_home do 169 | mode '700' 170 | group 'g'+test_user 171 | user test_user 172 | end 173 | test_venv = python_virtualenv ::File.join(test_home, 'env') do 174 | python new_resource.name 175 | user test_user 176 | end 177 | python_package 'docopt' do 178 | user test_user 179 | virtualenv test_venv 180 | end 181 | test_import('docopt', python: nil, virtualenv: test_venv, user: test_user) 182 | end 183 | 184 | end 185 | end 186 | 187 | def sentinel_file(name) 188 | file ::File.join(new_resource.path, "sentinel_#{name}") do 189 | action :nothing 190 | end 191 | end 192 | 193 | private 194 | 195 | def test_version(python: new_resource.name, virtualenv: nil) 196 | # Only queue up this resource once, the ivar is just for tracking. 197 | @python_version_test ||= file ::File.join(new_resource.path, 'python_version.py') do 198 | user node.platform_family?('windows') ? Poise::Utils::Win32.admin_user : 'root' 199 | group node['root_group'] 200 | mode '644' 201 | content <<-EOH 202 | import sys, platform 203 | open(sys.argv[1], 'w').write(platform.python_version()) 204 | EOH 205 | end 206 | 207 | python_execute "#{@python_version_test.path} #{::File.join(new_resource.path, 'version')}" do 208 | python python if python 209 | virtualenv virtualenv if virtualenv 210 | end 211 | end 212 | 213 | def test_import(name, path=name, python: new_resource.name, virtualenv: nil, user: nil) 214 | # Only queue up this resource once, the ivar is just for tracking. 215 | @python_import_test ||= file ::File.join(new_resource.path, 'import_version.py') do 216 | user node.platform_family?('windows') ? Poise::Utils::Win32.admin_user : 'root' 217 | group node['root_group'] 218 | mode '644' 219 | content <<-EOH 220 | try: 221 | import sys 222 | mod = __import__(sys.argv[1]) 223 | open(sys.argv[2], 'w').write(mod.__version__) 224 | except ImportError: 225 | pass 226 | EOH 227 | end 228 | 229 | python_execute "#{@python_import_test.path} #{name} #{::File.join(new_resource.path, "import_#{path}")}" do 230 | python python if python 231 | user user if user 232 | virtualenv virtualenv if virtualenv 233 | end 234 | end 235 | 236 | end 237 | end 238 | end 239 | end 240 | -------------------------------------------------------------------------------- /lib/poise_python/resources/python_virtualenv.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise' 18 | 19 | # Break a require loop by letting autoload work its magic. 20 | require 'poise_python' 21 | 22 | 23 | module PoisePython 24 | module Resources 25 | # (see PythonVirtualenv::Resource) 26 | # @since 1.0.0 27 | module PythonVirtualenv 28 | # A `python_virtualenv` resource to manage Python virtual environments. 29 | # 30 | # @provides python_virtualenv 31 | # @action create 32 | # @action delete 33 | # @example 34 | # python_virtualenv '/opt/myapp' 35 | class Resource < PoisePython::Resources::PythonRuntime::Resource 36 | include PoisePython::PythonCommandMixin 37 | provides(:python_virtualenv) 38 | # Add create and delete actions as more semantically relevant aliases. 39 | default_action(:create) 40 | actions(:create, :delete) 41 | 42 | # @!attribute path 43 | # Path to create the environment at. 44 | # @return [String] 45 | attribute(:path, kind_of: String, name_attribute: true) 46 | # @!attribute group 47 | # System group to create the virtualenv. 48 | # @return [String, Integer, nil] 49 | attribute(:group, kind_of: [String, Integer, NilClass]) 50 | # @!attribute system_site_packages 51 | # Enable or disable visibilty of system packages in the environment. 52 | # @return [Boolean] 53 | attribute(:system_site_packages, equal_to: [true, false], default: false) 54 | # @!attribute user 55 | # System user to create the virtualenv. 56 | # @return [String, Integer, nil] 57 | attribute(:user, kind_of: [String, Integer, NilClass]) 58 | 59 | # Lock the default provider. 60 | # 61 | # @api private 62 | def initialize(*args) 63 | super 64 | # Sidestep all the normal provider lookup stuffs. This is kind of 65 | # gross but it will do for now. The hard part is that the base classes 66 | # for the resource and provider are using Poise::Inversion, which we 67 | # don't want to use for python_virtualenv. 68 | @provider = Provider 69 | end 70 | 71 | # Upstream attribute we don't support. Sets are an error and gets always 72 | # return nil. 73 | # 74 | # @api private 75 | # @param arg [Object] Ignored 76 | # @return [nil] 77 | def version(arg=nil) 78 | raise NoMethodError if arg 79 | end 80 | 81 | # (see #version) 82 | def virtualenv_version(arg=nil) 83 | raise NoMethodError if arg 84 | end 85 | end 86 | 87 | # The default provider for `python_virtualenv`. 88 | # 89 | # @see Resource 90 | # @provides python_virtualenv 91 | class Provider < PoisePython::PythonProviders::Base 92 | include PoisePython::PythonCommandMixin 93 | provides(:python_virtualenv) 94 | 95 | # Alias our actions. Slightly annoying that they will show in 96 | # tracebacks with the original names, but oh well. 97 | alias_method :action_create, :action_install 98 | alias_method :action_delete, :action_uninstall 99 | 100 | def python_binary 101 | if node.platform_family?('windows') 102 | ::File.join(new_resource.path, 'Scripts', 'python.exe') 103 | else 104 | ::File.join(new_resource.path, 'bin', 'python') 105 | end 106 | end 107 | 108 | def python_environment 109 | if new_resource.parent_python 110 | new_resource.parent_python.python_environment 111 | else 112 | {} 113 | end 114 | end 115 | 116 | private 117 | 118 | def install_python 119 | return if ::File.exist?(python_binary) 120 | 121 | cmd = python_shell_out(%w{-m venv -h}) 122 | if cmd.error? 123 | converge_by("Creating virtualenv at #{new_resource.path}") do 124 | create_virtualenv(%w{virtualenv}) 125 | end 126 | else 127 | converge_by("Creating venv at #{new_resource.path}") do 128 | use_withoutpip = cmd.stdout.include?('--without-pip') 129 | create_virtualenv(use_withoutpip ? %w{venv --without-pip} : %w{venv}) 130 | end 131 | end 132 | end 133 | 134 | def uninstall_python 135 | directory new_resource.path do 136 | action :delete 137 | recursive true 138 | end 139 | end 140 | 141 | # Don't install virtualenv inside virtualenv. 142 | # 143 | # @api private 144 | # @return [void] 145 | def install_virtualenv 146 | # This space left intentionally blank. 147 | end 148 | 149 | # Create a virtualenv using virtualenv or venv. 150 | # 151 | # @param driver [Array] Command snippet to actually make it. 152 | # @return [void] 153 | def create_virtualenv(driver) 154 | cmd = %w{-m} + driver 155 | cmd << '--system-site-packages' if new_resource.system_site_packages 156 | cmd << new_resource.path 157 | python_shell_out!(cmd, environment: { 158 | # Use the environment variables to cope with older virtualenv not 159 | # supporting --no-wheel. The env var will be ignored if unsupported. 160 | 'VIRTUALENV_NO_PIP' => '1', 161 | 'VIRTUALENV_NO_SETUPTOOLS' => '1', 162 | 'VIRTUALENV_NO_WHEEL' => '1', 163 | }, group: new_resource.group, user: new_resource.user) 164 | end 165 | 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /lib/poise_python/utils.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_python/error' 18 | 19 | 20 | module PoisePython 21 | # Helper methods for Python-related things. 22 | # 23 | # @since 1.0.0 24 | module Utils 25 | autoload :PythonEncoder, 'poise_python/utils/python_encoder' 26 | extend self 27 | 28 | # Convert an object to a Python literal. 29 | # 30 | # @param obj [Object] Ovject to convert. 31 | # @return [String] 32 | def to_python(obj) 33 | PythonEncoder.new(obj).encode 34 | end 35 | 36 | # Convert path to a Python dotted module name. 37 | # 38 | # @param path [String] Path to the file. If base is not given, this must be 39 | # a relative path. 40 | # @param base [String] Optional base path to treat the file as relative to. 41 | # @return [String] 42 | def path_to_module(path, base=nil) 43 | if base 44 | path = ::File.expand_path(path, base) 45 | raise PoisePython::Error.new("Path #{path} is not inside base path #{base}") unless path.start_with?(base) 46 | path = path[base.length+1..-1] 47 | end 48 | path = path[0..-4] if path.end_with?('.py') 49 | path.gsub(/#{::File::SEPARATOR}/, '.') 50 | end 51 | 52 | # Convert a Python dotted module name to a path. 53 | # 54 | # @param mod [String] Dotted module name. 55 | # @param base [String] Optional base path to treat the file as relative to. 56 | # @return [String] 57 | def module_to_path(mod, base=nil) 58 | path = mod.gsub(/\./, ::File::SEPARATOR) + '.py' 59 | path = ::File.join(base, path) if base 60 | path 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/poise_python/utils/python_encoder.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'json' 18 | 19 | 20 | module PoisePython 21 | module Utils 22 | # Convert Ruby data structures to a Python literal. Overall similar to JSON 23 | # but just different enough that I need to write this. Thanks Obama. 24 | # 25 | # @since 1.0.0 26 | # @api private 27 | class PythonEncoder 28 | def initialize(root, depth_limit: 100) 29 | @root = root 30 | @depth_limit = depth_limit 31 | end 32 | 33 | def encode 34 | encode_obj(@root, 0) 35 | end 36 | 37 | private 38 | 39 | def encode_obj(obj, depth) 40 | raise ArgumentError.new("Depth limit exceeded") if depth > @depth_limit 41 | case obj 42 | when Hash 43 | encode_hash(obj, depth) 44 | when Array 45 | encode_array(obj, depth) 46 | when true 47 | 'True' 48 | when false 49 | 'False' 50 | when nil 51 | 'None' 52 | else 53 | obj.to_json 54 | end 55 | end 56 | 57 | def encode_hash(obj, depth) 58 | middle = obj.map do |key, value| 59 | "#{encode_obj(key, depth+1)}:#{encode_obj(value, depth+1)}" 60 | end 61 | "{#{middle.join(',')}}" 62 | end 63 | 64 | def encode_array(obj, depth) 65 | middle = obj.map do |value| 66 | encode_obj(value, depth+1) 67 | end 68 | "[#{middle.join(',')}]" 69 | end 70 | 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/poise_python/version.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | module PoisePython 19 | VERSION = '1.7.1.pre' 20 | end 21 | -------------------------------------------------------------------------------- /poise-python.gemspec: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | lib = File.expand_path('../lib', __FILE__) 18 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 19 | require 'poise_python/version' 20 | 21 | Gem::Specification.new do |spec| 22 | spec.name = 'poise-python' 23 | spec.version = PoisePython::VERSION 24 | spec.authors = ['Noah Kantrowitz'] 25 | spec.email = %w{noah@coderanger.net} 26 | spec.description = "A Chef cookbook for managing Python installations." 27 | spec.summary = spec.description 28 | spec.homepage = 'https://github.com/poise/poise-python' 29 | spec.license = 'Apache-2.0' 30 | spec.metadata['platforms'] = 'ubuntu debian redhat centos fedora amazon windows' 31 | 32 | spec.files = `git ls-files`.split($/) 33 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 34 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 35 | spec.require_paths = %w{lib} 36 | 37 | spec.add_dependency 'chef', '>= 12.16', '< 15' 38 | spec.add_dependency 'halite', '~> 1.0' 39 | spec.add_dependency 'poise', '~> 2.7' 40 | spec.add_dependency 'poise-languages', '~> 2.0' 41 | 42 | spec.add_development_dependency 'poise-boiler', '~> 1.8' 43 | end 44 | -------------------------------------------------------------------------------- /test/cookbook/metadata.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | name 'poise-python_test' 18 | depends 'poise-python' 19 | -------------------------------------------------------------------------------- /test/cookbook/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_python/resources/python_runtime_test' 18 | 19 | # Install lsb-release because Debian 6 doesn't by default and serverspec requires it 20 | package 'lsb-release' if platform?('debian') && node['platform_version'].start_with?('6') 21 | 22 | # Which tests to run on each platform. 23 | tests_to_run = value_for_platform( 24 | default: %w{py2 py3 system pypy pip}, 25 | centos: {default: %w{py2 py3 system scl pypy pip}}, 26 | redhat: {default: %w{py2 py3 system scl pypy pip}}, 27 | ubuntu: { 28 | '12.04' => %w{py2 pypy pip}, 29 | 'default' => %w{py2 py3 system pypy pip}, 30 | }, 31 | windows: {default: %w{py2 py3}}, 32 | ) 33 | 34 | %w{py2 py3 system pypy scl pip msi}.each do |test| 35 | unless tests_to_run.include?(test) 36 | file "/no_#{test}" 37 | next 38 | end 39 | 40 | case test 41 | when 'py2' 42 | python_runtime_test '2' 43 | when 'py3' 44 | python_runtime_test '3' 45 | when 'system' 46 | python_runtime_test 'system' do 47 | version '' 48 | runtime_provider :system 49 | end 50 | when 'scl' 51 | python_runtime_test 'scl' do 52 | version '' 53 | runtime_provider :scl 54 | end 55 | when 'pypy' 56 | python_runtime_test 'pypy' 57 | when 'pip' 58 | # Some pip-related tests that I don't need to run on every Python version. 59 | # Pip does plenty of testing on different Python versions and I already touch 60 | # the basics. 61 | pip_provider = value_for_platform_family(default: :portable_pypy, windows: :msi) 62 | # Check the baseline state, should pull the latest pip. 63 | python_runtime 'pip1' do 64 | provider pip_provider 65 | options path: '/test_pip1' 66 | version '' 67 | end 68 | # Check installing a requested version. 69 | python_runtime 'pip2' do 70 | pip_version '8.1.2' 71 | provider pip_provider 72 | options path: '/test_pip2' 73 | version '' 74 | end 75 | # Check installing the latest and reverting to an old version. 76 | python_runtime 'pip3' do 77 | provider pip_provider 78 | options path: '/test_pip3' 79 | version '' 80 | end 81 | python_runtime 'pip3b' do 82 | pip_version '7.1.2' 83 | provider pip_provider 84 | options path: '/test_pip3' 85 | version '' 86 | end 87 | # Test pip9 specifically just to be safe. 88 | python_runtime 'pip4' do 89 | pip_version '9.0.3' 90 | provider pip_provider 91 | options path: '/test_pip4' 92 | version '' 93 | end 94 | # Run a simple package install on each just to test things. 95 | (1..4).each do |n| 96 | python_package 'structlog' do 97 | python "pip#{n}" 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/docker/docker.ca: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCzCCAvOgAwIBAgIJAJTJgn9tSdKmMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV 3 | BAMTAkNBMB4XDTE1MDExMjIwMjk0M1oXDTI1MDEwOTIwMjk0M1owDTELMAkGA1UE 4 | AxMCQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDPFn79sz1kQLk 5 | rAS5z/zfCMXuE+V9IEmGXJeSprsXrv+AdjFpVDr52lpvZ36i2gixk8grcWtFqQMC 6 | jB1c2HUe69ebC89rHSPmGCx5eRcWQPQG29fdH/nC+I34EbYadJB7PdzkvTj0KuN8 7 | YfQj6lhwqltYiELZhuGoXcuhwZ5SC4VcJ2cdvx7oQPECLlMft8dhWyk15WGhp0jL 8 | 2H5noGajz9IFzHieoKJyh+oYA3BYCugpNBLTweNw+NuRxMwHixftvkXvlqKeZ402 9 | 4iwmIO8MG9oUxXs6D85gv6tJOau+dD1EDYH9VzYwSvLto3QBzJX0NiHKlmeq4BG2 10 | 1V3n+N1kZmMDgEtX2TDFsGHlUo77Gw0ob/w7qJU+7GAXRwWw7TPhMBLSkOlGM726 11 | Lq9p5+7mK1YThk0PmlsSAU6/fT79PSdrYpTKr3WkBTnwd76df+Koh8fpN4BHf9L4 12 | 9bWSYc9Nb9/wp0md9xhzjjVHargpVxZmNH7bcIa8YA9tYaW+oifo2hfb7o0qhGQ1 13 | 8pES3LPUi/qtZkBYUQdh8/mkqTvRjeS446iUmYWcrHyiIzQk/cMbrAVYOi3Xnq0J 14 | ui/r48iv7uLhpcDEQl2mENr0syygrPthVKa4gYHAZ0tK3pfe0yUGMiwS2D23xMR4 15 | WYLWLwYSK0j1JYpEbsBNS3wZX91FIwIDAQABo24wbDAdBgNVHQ4EFgQU1j2CHhNt 16 | sWAvDmu49yRFfHRBp9kwPQYDVR0jBDYwNIAU1j2CHhNtsWAvDmu49yRFfHRBp9mh 17 | EaQPMA0xCzAJBgNVBAMTAkNBggkAlMmCf21J0qYwDAYDVR0TBAUwAwEB/zANBgkq 18 | hkiG9w0BAQUFAAOCAgEAD7apefon65k3Xey7vsTb/A18m7JwBNLB48ILNcSKVgO1 19 | iuSMCGNQ+4bNU4o9cTpRoijB3w4RY7IIaDlRcUg8FIO6kgEhjhiAjSSqJAaajOFc 20 | urxOmi9E7xYmTDqLxEGF5/5vaG4olAi3tRgZNd2+Ue0ANZ1KMh3ZkE0nA5v1zb/g 21 | Ax/Zs6tATdoG6umMQg8TjiKucwi9J9he+xJ5y0E77/RrdNL9aKcU47wTAwUkokpb 22 | u1JFo1da3yZLDwQuBN5DCc4pgPgxXlfa6DnzQM1veKIhP5sa9T4sCC8S4IjGFenw 23 | yl4xm+9AOZQeLFpczqgVJhun5P41syepnZ433hWoLXKLHd1n0ILgw9JyVF686LIt 24 | bSar3+krmFuzdRCfet0kJR762p8jmxJOwL+KQGELGlkleJK48a+ruWIeeulZhpJ5 25 | tF4QJxytq4aXpjeFma0Yi/0rQuNi3H1QIW5YPnFL0XlJ8Rvr8gSVc1zhkM9rsnxX 26 | l9Pun0flP/mf/ulOa020hQUPqEYjSfdJOkLy2gZDvHRL2LRXNjGHoteGNJCq34Q1 27 | wQerxofHn+Hpp61+Ebj+RLK0KJE+QeP3T8rL30aSSzQZQZJVI0ict5C71kiTbQnw 28 | Z0vE6LquvFfMSqfPLt6uuCRVywBjLx19B7TuMf/DgAD+lR+1FFGKy1hO2Q1jfCY= 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /test/docker/docker.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFBTCCAu2gAwIBAgIBGTANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDEwJDQTAe 3 | Fw0xNTA2MjQxNjI5MDRaFw0yNTA2MjExNjI5MDRaMGYxJzAlBgNVBAMTHkRvY2tl 4 | ciBjbGllbnQgZm9yIHBvaXNlLXB5dGhvbjEXMBUGA1UECxMOa2l0Y2hlbi1kb2Nr 5 | ZXIxIjAgBgNVBAoTGUNvZGVyYW5nZXIgQ29uc3VsdGluZyBMTEMwggIiMA0GCSqG 6 | SIb3DQEBAQUAA4ICDwAwggIKAoICAQCvQ5r/r09lmqPAlmyaZvt83Ctkiz+40m79 7 | v1Ut4eKjGbmMAOgFee6aJQJBvYDBG8woImyHb2o/Cc8lvgIGosa/Sq5NvRqCy8dR 8 | 2Bx0r0w1KeLSB0Z/rJj05PwvoUHWjL9FmM/oJCaJR+tQqJepNV7LDUeNQ7CUKqdo 9 | cHthEti8CXzRP5waLJMHUg4RsXBb7+0f4O++Lq8iU4e2ifkZMHhLczlsQdU38Th2 10 | teTp4uNg872t0WdCYUoqcVagviMRTIji5nUQDXCiMyNRL4dzt3Hi5q9UWSw7TQGe 11 | SAwiDC37x2ct3SNvI6H+5QSbEGgtlJ8gJC5Gq6UqTfKK+8xDy3eWJyCzP+g9soC+ 12 | JMNczFhUyEX68eYHUro2Tb/PKiVZa5fzON2sEYAIbLEZ9HORyPI4YDPVyk/DBhXA 13 | xtWkAHiybXph3Z/2WYWK6tRC5C6LjuBnm5yHDwioWF3XhozK9lptLrKix2MpV8x7 14 | pI7qGC3ycWTWrQkyU/dr2dDQm1Qp2XAhVqu4i8Lo+VcRJG+rZsQznZTQXbvulBa4 15 | fIrs7Rgg3Sd6KJxQAo5mCHI9tdkjh5LVJgQfPX2b5mjTl5o+TEIEK8t4hsKUThHF 16 | 2kNzv/PaIKJmRC2R6Tqt5hce582VTr6s+9HKLxRBUwCSPwZlxeOfLQUOh245o/i8 17 | czc0eAtHjwIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0B 18 | AQUFAAOCAgEAIne7ZezRxVsI7iX0KP8IGPcjSQoz/oOwJmYqZDoEJnxGp1pG4YfG 19 | gb0TzO1tsQzx9jczxaZet12OXGNa+8PDRCjxm+8+bS6Oo2GDjSIxeJbC8duip37F 20 | 2dn5VWRf8D9C5GC/G1Ylk3KavdA6ye5Q3eaL/wPHzPwmnhEd1QZljx89Fftj+qAv 21 | 60No8RJwtfrfALzAroqwiihY6ubv1FXnUY6hEeAoxlFCrpMwV+7Y6uSjNNtYyLRk 22 | 7zW535GmDsYOpLnXdk4N0t+TJBu46mw2hX+yz7mXpYcsCcNLd9M4+Fyyri07qQkR 23 | VMMVBfnvgQA+zFB7FpNOY31lR1FL4DDUYY03fgtGe3+4Boxed1v3j1TBkUvAksOS 24 | UDH+FFiWzms1EVXeYBb1RCtBGicq9OwVB61KKrcwku4V9ym0HKzQfCzKENQQbZrN 25 | CPLeG+/fyXofCCR7qcAxpUoQj8yhMONyGZFVcDORTDUf5ckn3gDGjaPH3ycbw2IE 26 | yK5k9Hu2nFB505tzAfwSBCmsBIjygSvBj3NmCPfs3cBVxn/XjwtqTcCdagWd8O58 27 | TOm06tT6uA/gQwNCZk7j/UEJNS4G2tmnfNltuunOt+mxFMN6xbLRKpCzxoXVrcnf 28 | cFsmnqUhbAt1Q4EICxSzk7RQ3jrI1pJLELuUeXhF8ffmjUw3Jmitdd8= 29 | -----END CERTIFICATE----- 30 | -----BEGIN RSA PRIVATE KEY----- 31 | Proc-Type: 4,ENCRYPTED 32 | DEK-Info: AES-256-CBC,F879A95B0E208D4A8C16375B3206CCAC 33 | 34 | xuODVH6KSi0YuEW25bFt8vJR4gTbIYMxHTVLcHdkPrsrVGgnEKzEnlvsOiA0M45A 35 | 3ehgRyc+BhMFchfp0iXczHR89NY+4pza5rssidmsFm2gaZxhOsunEr3yRvWzn3OH 36 | vJT0Ws85vSKUtmQilmEQ7AACc/NhlJ5AdiwMpWWbo7Wa/DQ28Ml5/7dqeIftBk09 37 | 6o+xn2IniQy4EpUwhOYu3brnyuqTglZrpLq+od6DsWoWPC6eiSV1ymEw32HjxM76 38 | 1kl7aNqvowHEF4TqUDOl0vu+FayAKca3X5CoDmhNQtB/qeSh3nJoL0h6PdaP4EhS 39 | +c5zyMq2a4vVf/UDAs+624TxHJBNLHllIebAgPM898lxNOIQ/PILCe5SjC6v7Pij 40 | 0CkPQyOa4NR8pFaBfAMyatb9AcAcKo/NIYwLWhgPDH1MC653Df30nQYY/2EvLaod 41 | vpmQ/6TorxdXHTn0hHVRe7njaXlupegpTNlaIfKS2LRT0mmVO3+k+dzAj6tDRH20 42 | MoHBE4MspLSeUR1fi7e1CfpeQaYFJ/THg/4wGJHDgTvu3Z3B++N5vQrzIOO6/EOw 43 | kegtm7zpWthnKOC5Iypihgspmcie7mZ4D4wLWnu8hXxPu2LG5pWWYk5iUoMYg3ZK 44 | FQVBffYSOSwR8WwTPRzM2huFfwJ0021n9V3vB+jvjq5qXPI03yvAcZ6PFbMTfJ3S 45 | QlEVThtkevnHsldbJSurHl7YYiMVwosFXov3IRIYhsdH5BGQz/ZGueKBxmVOJ+Oj 46 | TO/b/rveEB7a3SYjVEvJXjmiD60D3mZlW3wuov1PrnWj14/Uo9T+vD8nFHRa7KQB 47 | rcnzy7v2rqgOJ2bucf4WIHQ/NijuUitT0mAk1pDHdWAvfVwL5U7f6VnaAyuaEkCB 48 | eGZbtDj0bymbXLO1NxyKGYi+4+BhoRYO9gxieBdg9qJWbI5sUHbNOIOQS0+t5stc 49 | NVA87oMQ3rWP+wd7NAOegmWRzh4ZzvAO3WCHUKVTzRFaaXq7f2iM9JstN97keJXC 50 | goPRh9HpKdFEYnR0djVvMj93ObxqxPrI8oysdSTl+kh9d85OUiC8NGpUqNA532xQ 51 | 6Bi6UPNuv7d4Jk9tpj2QDAW7vRl0CrYflWW+ULR3YA48488YRypdu0fUXklqRQ/+ 52 | WzibGUNDuyjCqm96rxWtl71F3DfY/LJYPGMzG2lWIiXvq5xl2fQ5DwEkhyPClIjK 53 | tljK0PiB+rY+zet3kKVmi53EjRsPR1LO/+L4mJ7GHjLljpm5BPKyMITsXqjTOwCT 54 | vLiRiAg9rGRs1vC51Bc5U1D8GU9pjJcX6M+IhQNPXwHMiN+iql090mWrcdCkKGkv 55 | k2dTOJATDOQ31tHUZl4CGXReVpLKPezUrvSu3yyTUZRCteC6gWfHX0INoQ//7EUq 56 | SkbWGX5DsPUOGplWYoGPdGSLhpRJFg/nSIiL4IHmX/dJQEceAPqBCoZsX8b7pV5Z 57 | 0AS9I0qNJU8vZutMxfSS5nO1iZFzUqYLktxeP9T4cDiFK1l+95al+XXbtjproW6I 58 | 0xkIuYiwmwnukf05ejIKFzQXXydjjFThgEnQkZgVeyEaNJEPZTQETYWzR5ILLFHX 59 | YVI0JIMqbzH/ocGXz6NZ7D97Q4Bz7sEb+yX5JkJMx8lQMtQlxWhEhs7As0FPiImn 60 | oKwhPJOqLGvCcCqnxmsPyfsZpJjHMqm2327E6jGowEu/sFwnxcqLEMEXJRyGZzQH 61 | q3hO7FyEZPh2QjZP7TiF3NO8wf4a0O05Syf0cMZy35f9bRdp19ESUmNEhycM6Vou 62 | bXxepzqvLf36FI1311o6AxlElKKL9KxNgZLlF0Q4IpO2KZkmYZCIJtYHcy509ll8 63 | NBWf/mcEFNZtjfLLexNztTdQ43lFTRd2ddErznCvHLbpsOoac+oaLAo09E2EUcm3 64 | 4yhMqDEFqACYd7AhiHvt18k4EybpZ5NIg2A4NxS6nEt/jyV90HGriHZxj675nYEL 65 | gB1F00sEv6ihrnd2y90AJI4K1MNGtJc0nB0R5gi4l/sLRp/lTOqV4Fn0wR/qFJ++ 66 | 4XemaOqhLLuQEi9PPMSxFazZpwd0k1OoPuogCsrH4ujzV3xpxrzAzpCsocwh0sZK 67 | J7AguBD+C9sZZOkGLT4kDDeNUJY7OCjdnY5yLjNpLqn+bTqidOUjRtmGzcrZXzh5 68 | BrmcpfQxzOl3H6e+0rUPr32YC4GF/IrbpDzT8lnceO4QYc3lTZEdqLi0ZjPfrNXm 69 | 4CBOfDQfQRvKel6dLSeJAQ6nRmh8bGMA3ZX5PTyb5p9rMXNSA6QJGIyP+5VTbbSP 70 | DKdmcbO7hs0m2tJypiTgOh+8cwsoDLgoA1UKzUvNq349AaYT7Y4hMEGNvetDhjEE 71 | 54unpiHlzvzO8VZ41W7mYiYduU8YwFUF8gAPC0B+71jCBqLqgq5XlLzHvn/9+32h 72 | E9rBv9tIt8r8GbnfdTfz83fhpg7ZJZeY3nv7qPyFM9wfgRxwWq741KLmzBoj4T6P 73 | 9puy8FjxjHw0mN+U4euVuoqiIil5UHLuecGg5Kx/bbR177Oax3vANwpFtnQCT34f 74 | oqsjjvqii3VijWxt9ynAtPX5flgRmmWmSAnqWlrlr2AvkJBO7R0ID4F0Sq1k3245 75 | V1fv7BXVt+hnWVrYo+UKjAGa/nqmJVVyhHvAgfntp//h+HrpK7ww+NwVH4gE+K4S 76 | q4m7uMsH2fnTopjYJaNZN7MwV/wPFpWBlEogVGIV7L99oF/0Vzwj14vKhDMIXhgQ 77 | DsbF9vrLwEMCnsm/d8sWjT/mVEznC+A5PI5DDmcORUAY4UEF55NjL/2U5ddmH4+3 78 | S9KeNURiBdRoYhnUD2nAvTc8EoK+BR9/vpYhlk/V0KGlRGt8GdsuH/BW11mRg4kY 79 | z2K/BdOSLZfJYtHR2nPkUezprnSocQQilMT6/UIx2Tt07ZFB59TDoBT0AX6QuI3w 80 | BCnGmJWul7S8Q4olipE7EYg6nS2YERUzJFTiCVAwTPkhH8TycdmxrNaZ9+Vo5W6t 81 | MIzb5HJo2n4S17BTif02O89Pm+QDnXYRUO9Go178+rnPODGrmup5Tct8E30QD9kI 82 | 12FXoN/OaSnKbSskQzOc+HT36QCVFWhXcU7GZC0LPP2jQNkjYVFJnxS+wQlfrcT3 83 | -----END RSA PRIVATE KEY----- 84 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.16.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.16.42' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.17.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.17.44' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.18.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.18.31' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.19.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.19.36' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.20.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.20.3' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.21.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.21.31' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-12.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 12.21' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.0.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.0.118' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.1.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.1.31' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.10.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.10.4' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.2.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.2.20' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.3.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.3.42' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.4.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.4.24' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.5.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.5.3' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.6.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.6.4' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.8.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.8.0' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.9.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.9.4' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-13.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 13.10' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.0.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.0.202' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.1.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.1.12' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.2.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.2.0' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.3.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.3.37' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.4.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.4.56' 20 | -------------------------------------------------------------------------------- /test/gemfiles/chef-14.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', '~> 14.4' 20 | -------------------------------------------------------------------------------- /test/gemfiles/master.gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | eval_gemfile File.expand_path('../../../Gemfile', __FILE__) 18 | 19 | gem 'chef', git: 'https://github.com/chef/chef.git' 20 | gem 'chefspec', git: 'https://github.com/sethvargo/chefspec.git' 21 | gem 'fauxhai', git: 'https://github.com/customink/fauxhai.git' 22 | gem 'foodcritic', git: 'https://github.com/foodcritic/foodcritic.git' 23 | # gem 'halite', git: 'https://github.com/poise/halite.git' 24 | gem 'ohai', git: 'https://github.com/chef/ohai.git' 25 | gem 'poise', git: 'https://github.com/poise/poise.git' 26 | gem 'poise-archive', git: 'https://github.com/poise/poise-archive.git' 27 | gem 'poise-boiler', git: 'https://github.com/poise/poise-boiler.git' 28 | gem 'poise-languages', git: 'https://github.com/poise/poise-languages.git' 29 | gem 'poise-profiler', git: 'https://github.com/poise/poise-profiler.git' 30 | -------------------------------------------------------------------------------- /test/integration/default/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'rbconfig' 18 | 19 | require 'serverspec' 20 | 21 | if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ 22 | set :backend, :cmd 23 | set :os, :family => 'windows' 24 | else 25 | set :backend, :exec 26 | end 27 | 28 | # Set up the shared example for python_runtime_test. 29 | RSpec.shared_examples 'a python_runtime_test' do |python_name, version=nil| 30 | let(:python_name) { python_name } 31 | let(:python_path) { File.join('', "python_test_#{python_name}") } 32 | # Helper for all the file checks. 33 | def self.assert_file(rel_path, should_exist=true, &block) 34 | describe rel_path do 35 | subject { file(File.join(python_path, rel_path)) } 36 | # Do nothing for nil. 37 | if should_exist == true 38 | it { is_expected.to be_a_file } 39 | elsif should_exist == false 40 | it { is_expected.to_not exist } 41 | end 42 | instance_eval(&block) if block 43 | end 44 | end 45 | 46 | describe 'python_runtime' do 47 | assert_file('version') do 48 | its(:content) { is_expected.to start_with version } if version 49 | end 50 | end 51 | 52 | describe 'python_package' do 53 | describe 'sqlparse' do 54 | assert_file('import_sqlparse_before', false) 55 | assert_file('import_sqlparse_mid') 56 | assert_file('import_sqlparse_after', false) 57 | assert_file('sentinel_sqlparse') 58 | assert_file('sentinel_sqlparse2', false) 59 | end 60 | 61 | describe 'setuptools' do 62 | assert_file('sentinel_setuptools', false) 63 | end 64 | 65 | describe 'pep8' do 66 | assert_file('import_pep8') 67 | end 68 | 69 | describe 'pytz' do 70 | assert_file('import_pytz') 71 | end 72 | end 73 | 74 | describe 'python_virtualenv' do 75 | assert_file('venv', nil) do 76 | it { is_expected.to be_a_directory } 77 | end 78 | assert_file('import_pytest', false) 79 | assert_file('import_pytest_venv') 80 | end 81 | 82 | describe 'pip_requirements' do 83 | assert_file('import_requests') do 84 | its(:content) { is_expected.to match /^2\.7\.0\s*$/ } 85 | end 86 | assert_file('import_six') do 87 | its(:content) { is_expected.to match /^1\.8\.0\s*$/ } 88 | end 89 | end 90 | 91 | describe 'non default version' do 92 | assert_file('import_requests_version') do 93 | its(:content) { is_expected.to match /^2\.8\.0\s*$/ } 94 | end 95 | end 96 | 97 | unless os[:family] == 'windows' 98 | describe 'user install' do 99 | assert_file('import_docopt') 100 | end 101 | end 102 | end 103 | 104 | describe 'python 2', unless: File.exist?('/no_py2') do 105 | it_should_behave_like 'a python_runtime_test', '2', '2' 106 | end 107 | 108 | describe 'python 3', unless: File.exist?('/no_py3') do 109 | it_should_behave_like 'a python_runtime_test', '3', '3' 110 | end 111 | 112 | describe 'pypy', unless: File.exist?('/no_pypy') do 113 | it_should_behave_like 'a python_runtime_test', 'pypy' 114 | end 115 | 116 | describe 'system provider', unless: File.exist?('/no_system') do 117 | it_should_behave_like 'a python_runtime_test', 'system' 118 | end 119 | 120 | describe 'scl provider', unless: File.exist?('/no_scl') do 121 | it_should_behave_like 'a python_runtime_test', 'scl' 122 | end 123 | 124 | describe 'pip reversion test', unless: File.exist?('/no_pip') do 125 | path_suffix = if os[:family] == 'windows' 126 | '/Scripts/python.exe' 127 | else 128 | '/bin/pypy' 129 | end 130 | 131 | # Confirm pip verisons. 132 | describe command("/test_pip1#{path_suffix} -m pip --version") do 133 | its(:exit_status) { is_expected.to eq 0 } 134 | its(:stdout) { is_expected.to include ' 18.' } 135 | end 136 | 137 | describe command("/test_pip2#{path_suffix} -m pip --version") do 138 | its(:exit_status) { is_expected.to eq 0 } 139 | its(:stdout) { is_expected.to include '8.1.2' } 140 | end 141 | 142 | describe command("/test_pip3#{path_suffix} -m pip --version") do 143 | its(:exit_status) { is_expected.to eq 0 } 144 | its(:stdout) { is_expected.to include '7.1.2' } 145 | end 146 | 147 | describe command("/test_pip4#{path_suffix} -m pip --version") do 148 | its(:exit_status) { is_expected.to eq 0 } 149 | its(:stdout) { is_expected.to include '9.0.3' } 150 | end 151 | 152 | # Check that structlog installed. 153 | describe command("/test_pip1#{path_suffix} -c 'import structlog'") do 154 | its(:exit_status) { is_expected.to eq 0 } 155 | end 156 | 157 | describe command("/test_pip2#{path_suffix} -c 'import structlog'") do 158 | its(:exit_status) { is_expected.to eq 0 } 159 | end 160 | 161 | describe command("/test_pip3#{path_suffix} -c 'import structlog'") do 162 | its(:exit_status) { is_expected.to eq 0 } 163 | end 164 | 165 | describe command("/test_pip4#{path_suffix} -c 'import structlog'") do 166 | its(:exit_status) { is_expected.to eq 0 } 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /test/spec/python_command_mixin_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonCommandMixin do 20 | describe PoisePython::PythonCommandMixin::Resource do 21 | resource(:poise_test) do 22 | include described_class 23 | end 24 | provider(:poise_test) 25 | 26 | describe '#python' do 27 | let(:python) { chef_run.python_runtime('test') } 28 | 29 | context 'with an implicit parent' do 30 | recipe do 31 | python_runtime 'test' do 32 | provider :dummy 33 | end 34 | poise_test 'test' 35 | end 36 | 37 | it { is_expected.to run_poise_test('test').with(parent_python: python, python: '/python') } 38 | end # /context with an implicit parent 39 | 40 | context 'with a parent resource' do 41 | recipe do 42 | r = python_runtime 'test' do 43 | provider :dummy 44 | end 45 | poise_test 'test' do 46 | python r 47 | end 48 | end 49 | 50 | it { is_expected.to run_poise_test('test').with(parent_python: python, python: '/python') } 51 | end # /context with a parent resource 52 | 53 | context 'with a parent resource name' do 54 | recipe do 55 | python_runtime 'test' do 56 | provider :dummy 57 | end 58 | poise_test 'test' do 59 | python 'test' 60 | end 61 | end 62 | 63 | it { is_expected.to run_poise_test('test').with(parent_python: python, python: '/python') } 64 | end # /context with a parent resource name 65 | 66 | context 'with a parent resource name that looks like a path' do 67 | let(:python) { chef_run.python_runtime('/usr/bin/other') } 68 | recipe do 69 | python_runtime '/usr/bin/other' do 70 | provider :dummy 71 | end 72 | poise_test 'test' do 73 | python '/usr/bin/other' 74 | end 75 | end 76 | 77 | it { is_expected.to run_poise_test('test').with(parent_python: python, python: '/python') } 78 | end # /context with a parent resource name that looks like a path 79 | 80 | context 'with a path' do 81 | recipe do 82 | poise_test 'test' do 83 | python '/usr/bin/other' 84 | end 85 | end 86 | 87 | it { is_expected.to run_poise_test('test').with(parent_python: nil, python: '/usr/bin/other') } 88 | end # /context with a path 89 | 90 | context 'with a path and an implicit parent' do 91 | recipe do 92 | python_runtime 'test' do 93 | provider :dummy 94 | end 95 | poise_test 'test' do 96 | python '/usr/bin/other' 97 | end 98 | end 99 | 100 | it { is_expected.to run_poise_test('test').with(parent_python: python, python: '/usr/bin/other') } 101 | end # /context with a path and an implicit parent 102 | 103 | context 'with an invalid parent' do 104 | recipe do 105 | poise_test 'test' do 106 | python 'test' 107 | end 108 | end 109 | 110 | it { expect { subject }.to raise_error Chef::Exceptions::ResourceNotFound } 111 | end # /context with an invalid parent 112 | 113 | end # /describe #python 114 | end # /describe PoisePython::PythonCommandMixin::Resource 115 | end 116 | -------------------------------------------------------------------------------- /test/spec/python_providers/dummy_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonProviders::Dummy do 20 | let(:python_runtime) { chef_run.python_runtime('test') } 21 | step_into(:python_runtime) 22 | recipe do 23 | python_runtime 'test' do 24 | provider :dummy 25 | end 26 | end 27 | 28 | describe '#python_binary' do 29 | subject { python_runtime.python_binary } 30 | 31 | it { is_expected.to eq '/python' } 32 | end # /describe #python_binary 33 | 34 | describe '#python_environment' do 35 | subject { python_runtime.python_environment } 36 | 37 | it { is_expected.to eq({}) } 38 | end # /describe #python_environment 39 | 40 | describe 'action :install' do 41 | # Just make sure it doesn't error. 42 | it { run_chef } 43 | end # /describe action :install 44 | 45 | describe 'action :uninstall' do 46 | recipe do 47 | python_runtime 'test' do 48 | action :uninstall 49 | provider :dummy 50 | end 51 | end 52 | 53 | # Just make sure it doesn't error. 54 | it { run_chef } 55 | end # /describe action :uninstall 56 | end 57 | -------------------------------------------------------------------------------- /test/spec/python_providers/portable_pypy3_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonProviders::PortablePyPy3 do 20 | let(:python_version) { nil } 21 | let(:chefspec_options) { {platform: 'ubuntu', version: '14.04'} } 22 | let(:default_attributes) { {poise_python_version: python_version} } 23 | let(:python_runtime) { chef_run.python_runtime('test') } 24 | step_into(:python_runtime) 25 | recipe do 26 | python_runtime 'test' do 27 | provider_no_auto 'dummy' 28 | version node['poise_python_version'] 29 | virtualenv_version false 30 | end 31 | end 32 | 33 | shared_examples_for 'portablepypy3 provider' do |base, url=nil| 34 | it { expect(python_runtime.provider_for_action(:install)).to be_a described_class } 35 | it { is_expected.to install_poise_languages_static(File.join('', 'opt', base)).with(source: url || "https://bitbucket.org/squeaky/portable-pypy/downloads/#{base}-linux_x86_64-portable.tar.bz2") } 36 | it { expect(python_runtime.python_binary).to eq File.join('', 'opt', base, 'bin', 'pypy') } 37 | end 38 | 39 | context 'with version pypy3' do 40 | let(:python_version) { 'pypy3' } 41 | it_behaves_like 'portablepypy3 provider', 'pypy3-2.4' 42 | end # /context with version pypy3 43 | 44 | context 'with version pypy3-2.3.1' do 45 | let(:python_version) { 'pypy3-2.3.1' } 46 | it_behaves_like 'portablepypy3 provider', 'pypy3-2.3.1' 47 | end # /context with version pypy3-2.3.1 48 | 49 | 50 | context 'with version pypy3-5.5' do 51 | let(:python_version) { 'pypy3-5.5' } 52 | it_behaves_like 'portablepypy3 provider', 'pypy3-5.5-alpha-20161014', 'https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3.3-5.5-alpha-20161014-linux_x86_64-portable.tar.bz2' 53 | end # /context with version pypy3-5.5 54 | 55 | context 'with version pypy3-5.7' do 56 | let(:python_version) { 'pypy3-5.7' } 57 | it_behaves_like 'portablepypy3 provider', 'pypy3-5.7.1-beta', 'https://bitbucket.org/squeaky/portable-pypy/downloads/pypy3.5-5.7.1-beta-linux_x86_64-portable.tar.bz2' 58 | end # /context with version pypy3-5.5 59 | 60 | context 'action :uninstall' do 61 | recipe do 62 | python_runtime 'test' do 63 | version 'pypy3' 64 | action :uninstall 65 | end 66 | end 67 | 68 | it { is_expected.to uninstall_poise_languages_static('/opt/pypy3-2.4') } 69 | end # /context action :uninstall 70 | end 71 | -------------------------------------------------------------------------------- /test/spec/python_providers/portable_pypy_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonProviders::PortablePyPy do 20 | let(:python_version) { nil } 21 | let(:chefspec_options) { {platform: 'ubuntu', version: '14.04'} } 22 | let(:default_attributes) { {poise_python_version: python_version} } 23 | let(:python_runtime) { chef_run.python_runtime('test') } 24 | step_into(:python_runtime) 25 | recipe do 26 | python_runtime 'test' do 27 | provider_no_auto 'dummy' 28 | version node['poise_python_version'] 29 | virtualenv_version false 30 | end 31 | end 32 | 33 | shared_examples_for 'portablepypy provider' do |base| 34 | it { expect(python_runtime.provider_for_action(:install)).to be_a described_class } 35 | it { is_expected.to install_poise_languages_static(File.join('', 'opt', base)).with(source: "https://bitbucket.org/squeaky/portable-pypy/downloads/#{base}-linux_x86_64-portable.tar.bz2") } 36 | it { expect(python_runtime.python_binary).to eq File.join('', 'opt', base, 'bin', 'pypy') } 37 | end 38 | 39 | context 'with version pypy' do 40 | let(:python_version) { 'pypy' } 41 | it_behaves_like 'portablepypy provider', 'pypy-5.7.1' 42 | end # /context with version pypy 43 | 44 | context 'with version pypy-2.4' do 45 | let(:python_version) { 'pypy-2.4' } 46 | it_behaves_like 'portablepypy provider', 'pypy-2.4' 47 | end # /context with version pypy-2.4 48 | 49 | context 'action :uninstall' do 50 | recipe do 51 | python_runtime 'test' do 52 | version 'pypy' 53 | action :uninstall 54 | end 55 | end 56 | 57 | it { is_expected.to uninstall_poise_languages_static('/opt/pypy-5.7.1') } 58 | end # /context action :uninstall 59 | end 60 | -------------------------------------------------------------------------------- /test/spec/python_providers/scl_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonProviders::Scl do 20 | let(:python_version) { '' } 21 | let(:chefspec_options) { {platform: 'centos', version: '7.4.1708'} } 22 | let(:default_attributes) { {poise_python_version: python_version} } 23 | let(:python_runtime) { chef_run.python_runtime('test') } 24 | step_into(:python_runtime) 25 | recipe do 26 | python_runtime 'test' do 27 | provider_no_auto 'dummy' 28 | version node['poise_python_version'] 29 | virtualenv_version false 30 | end 31 | end 32 | 33 | shared_examples_for 'scl provider' do |pkg| 34 | it { expect(python_runtime.provider_for_action(:install)).to be_a described_class } 35 | it { is_expected.to install_poise_languages_scl(pkg) } 36 | it do 37 | expect_any_instance_of(described_class).to receive(:install_scl_package) 38 | run_chef 39 | end 40 | end 41 | 42 | context 'with version ""' do 43 | let(:python_version) { '' } 44 | it_behaves_like 'scl provider', 'rh-python36' 45 | end # /context with version "" 46 | 47 | context 'with version "2"' do 48 | let(:python_version) { '2' } 49 | it_behaves_like 'scl provider', 'python27' 50 | end # /context with version "2" 51 | 52 | context 'with version "3"' do 53 | let(:python_version) { '3' } 54 | it_behaves_like 'scl provider', 'rh-python36' 55 | end # /context with version "3" 56 | 57 | context 'with version "3.3"' do 58 | let(:python_version) { '3.3' } 59 | it_behaves_like 'scl provider', 'python33' 60 | end # /context with version "3.3" 61 | 62 | context 'with version "" on CentOS 6' do 63 | let(:chefspec_options) { {platform: 'centos', version: '6.9'} } 64 | let(:python_version) { '' } 65 | it_behaves_like 'scl provider', 'rh-python36' 66 | end # /context with version "" on CentOS 6 67 | 68 | context 'action :uninstall' do 69 | recipe do 70 | python_runtime 'test' do 71 | action :uninstall 72 | version node['poise_python_version'] 73 | end 74 | end 75 | 76 | it do 77 | expect_any_instance_of(described_class).to receive(:uninstall_scl_package) 78 | run_chef 79 | end 80 | it { expect(python_runtime.provider_for_action(:uninstall)).to be_a described_class } 81 | end # /context action :uninstall 82 | end 83 | -------------------------------------------------------------------------------- /test/spec/python_providers/system_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::PythonProviders::System do 20 | let(:python_version) { '' } 21 | let(:chefspec_options) { {platform: 'ubuntu', version: '14.04'} } 22 | let(:default_attributes) { {poise_python_version: python_version} } 23 | let(:python_runtime) { chef_run.python_runtime('test') } 24 | let(:system_package_candidates) { python_runtime.provider_for_action(:install).send(:system_package_candidates, python_version) } 25 | step_into(:python_runtime) 26 | recipe do 27 | python_runtime 'test' do 28 | provider_no_auto 'dummy' 29 | version node['poise_python_version'] 30 | virtualenv_version false 31 | end 32 | end 33 | 34 | shared_examples_for 'system provider' do |candidates=nil, pkg| 35 | it { expect(python_runtime.provider_for_action(:install)).to be_a described_class } 36 | it { expect(system_package_candidates).to eq candidates } if candidates 37 | it { is_expected.to install_poise_languages_system(pkg) } 38 | it do 39 | expect_any_instance_of(described_class).to receive(:install_system_packages) 40 | run_chef 41 | end 42 | end 43 | 44 | context 'with version ""' do 45 | let(:python_version) { '' } 46 | it_behaves_like 'system provider', %w{python3.7 python37 python3.6 python36 python3.5 python35 python3.4 python34 python3.3 python33 python3.2 python32 python3.1 python31 python3.0 python30 python3 python2.7 python27 python2.6 python26 python2.5 python25 python}, 'python3.4' 47 | end # /context with version "" 48 | 49 | context 'with version 2' do 50 | let(:python_version) { '2' } 51 | it_behaves_like 'system provider', %w{python2.7 python27 python2.6 python26 python2.5 python25 python}, 'python2.7' 52 | end # /context with version 2 53 | 54 | context 'with version 3' do 55 | let(:python_version) { '3' } 56 | it_behaves_like 'system provider', %w{python3.7 python37 python3.6 python36 python3.5 python35 python3.4 python34 python3.3 python33 python3.2 python32 python3.1 python31 python3.0 python30 python3 python}, 'python3.4' 57 | end # /context with version 3 58 | 59 | context 'with version 2.3' do 60 | let(:python_version) { '2.3' } 61 | before do 62 | default_attributes['poise-python'] ||= {} 63 | default_attributes['poise-python']['test'] = {'package_name' => 'python2.3'} 64 | end 65 | it_behaves_like 'system provider', %w{python2.3 python23 python}, 'python2.3' 66 | end # /context with version 2.3 67 | 68 | context 'on Ubuntu 18.04' do 69 | let(:chefspec_options) { {platform: 'ubuntu', version: '18.04'} } 70 | let(:python_version) { '3.6' } 71 | 72 | it { is_expected.to install_package(%w{python3.6-venv python3.6-distutils}) } 73 | end # /context on Ubuntu 18.04 74 | 75 | context 'on Debian 8' do 76 | before { chefspec_options.update(platform: 'debian', version: '8.9') } 77 | it_behaves_like 'system provider', 'python3.4' 78 | end # /context on Debian 8 79 | 80 | context 'on CentOS 7' do 81 | before { chefspec_options.update(platform: 'centos', version: '7.4.1708') } 82 | recipe do 83 | python_runtime 'test' do 84 | provider :system 85 | version node['poise_python_version'] 86 | virtualenv_version false 87 | end 88 | end 89 | it_behaves_like 'system provider', 'python' 90 | end # /context on CentOS 7 91 | 92 | context 'action :uninstall' do 93 | recipe do 94 | python_runtime 'test' do 95 | action :uninstall 96 | version node['poise_python_version'] 97 | end 98 | end 99 | 100 | it do 101 | expect_any_instance_of(described_class).to receive(:uninstall_system_packages) 102 | run_chef 103 | end 104 | it { expect(python_runtime.provider_for_action(:uninstall)).to be_a described_class } 105 | end # /context action :uninstall 106 | end 107 | -------------------------------------------------------------------------------- /test/spec/resources/pip_requirements_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Resources::PipRequirements do 20 | let(:pip_cmd) { %w{-m pip.__main__ install --requirement /test/requirements.txt} } 21 | let(:pip_output) { '' } 22 | let(:pip_user) { nil } 23 | let(:pip_group) { nil } 24 | let(:pip_cwd) { '/test' } 25 | step_into(:pip_requirements) 26 | before do 27 | allow(File).to receive(:directory?).and_call_original 28 | allow(File).to receive(:directory?).with('/test').and_return(true) 29 | end 30 | before do 31 | expect_any_instance_of(PoisePython::Resources::PipRequirements::Provider).to receive(:python_shell_out!).with(pip_cmd, {user: pip_user, group: pip_group, cwd: pip_cwd}).and_return(double(stdout: pip_output)) 32 | end 33 | 34 | context 'with a directory' do 35 | recipe do 36 | pip_requirements '/test' 37 | end 38 | 39 | it { is_expected.to install_pip_requirements('/test') } 40 | end # /context with a directory 41 | 42 | context 'with a file' do 43 | let(:pip_cmd) { %w{-m pip.__main__ install --requirement /test/reqs.txt} } 44 | recipe do 45 | pip_requirements '/test/reqs.txt' 46 | end 47 | 48 | it { is_expected.to install_pip_requirements('/test/reqs.txt') } 49 | end # /context with a file 50 | 51 | context 'with a user' do 52 | let(:pip_user) { 'testuser' } 53 | recipe do 54 | pip_requirements '/test' do 55 | user 'testuser' 56 | end 57 | end 58 | 59 | it { is_expected.to install_pip_requirements('/test') } 60 | end # /context with a user 61 | 62 | context 'with a group' do 63 | let(:pip_group) { 'testgroup' } 64 | recipe do 65 | pip_requirements '/test' do 66 | group 'testgroup' 67 | end 68 | end 69 | 70 | it { is_expected.to install_pip_requirements('/test') } 71 | end # /context with a group 72 | 73 | context 'action :upgrade' do 74 | let(:pip_cmd) { %w{-m pip.__main__ install --upgrade --requirement /test/requirements.txt} } 75 | recipe do 76 | pip_requirements '/test' do 77 | action :upgrade 78 | end 79 | end 80 | 81 | it { is_expected.to upgrade_pip_requirements('/test') } 82 | end # /context action :upgrade 83 | 84 | context 'with output' do 85 | let(:pip_output) { 'Successfully installed' } 86 | recipe do 87 | pip_requirements '/test' 88 | end 89 | 90 | it { is_expected.to install_pip_requirements('/test').with(updated?: true) } 91 | end # /context with output 92 | 93 | context 'with a cwd' do 94 | let(:pip_cwd) { '/other' } 95 | recipe do 96 | pip_requirements '/test' do 97 | cwd '/other' 98 | end 99 | end 100 | 101 | it { is_expected.to install_pip_requirements('/test') } 102 | end # /context with a cwd 103 | 104 | context 'with options' do 105 | let(:pip_cmd) { '-m pip.__main__ install --index-url=http://example --requirement /test/requirements.txt' } 106 | recipe do 107 | pip_requirements '/test' do 108 | options '--index-url=http://example' 109 | end 110 | end 111 | 112 | it { is_expected.to install_pip_requirements('/test') } 113 | end # /context with options 114 | end 115 | -------------------------------------------------------------------------------- /test/spec/resources/python_package_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Resources::PythonPackage do 20 | describe PoisePython::Resources::PythonPackage::Resource do 21 | describe '#response_file' do 22 | recipe do 23 | python_package 'foo' do 24 | response_file 'bar' 25 | end 26 | end 27 | 28 | it { expect { subject }.to raise_error NoMethodError } 29 | end # /describe #response_file 30 | 31 | describe '#response_file_variables' do 32 | recipe do 33 | python_package 'foo' do 34 | response_file_variables 'bar' 35 | end 36 | end 37 | 38 | it { expect { subject }.to raise_error NoMethodError } 39 | end # /describe #response_file_variables 40 | 41 | describe '#source' do 42 | recipe do 43 | python_package 'foo' do 44 | source 'bar' 45 | end 46 | end 47 | 48 | it { expect { subject }.to raise_error NoMethodError } 49 | end # /describe #source 50 | end # /describe PoisePython::Resources::PythonPackage::Resource 51 | 52 | describe PoisePython::Resources::PythonPackage::Provider do 53 | let(:test_resource) { PoisePython::Resources::PythonPackage::Resource.new('package', chef_run.run_context) } 54 | let(:test_provider) { described_class.new(test_resource, chef_run.run_context) } 55 | def stub_cmd(cmd, **options) 56 | options = options.dup 57 | array_13 = options.delete(:array_13) 58 | if array_13 && Gem::Requirement.create('>= 13').satisfied_by?(Gem::Version.create(Chef::VERSION)) 59 | cmd = Shellwords.split(cmd) 60 | end 61 | output_options = {error?: options.delete(:error?) || false, stdout: options.delete(:stdout) || '', stderr: options.delete(:stderr) || ''} 62 | expect(test_provider).to receive(:python_shell_out!).with(cmd, options).and_return(double("python #{cmd} return", **output_options)) 63 | end 64 | 65 | describe '#load_current_resource' do 66 | let(:package_name) { nil } 67 | let(:package_version) { nil } 68 | let(:test_resource) { PoisePython::Resources::PythonPackage::Resource.new(package_name, chef_run.run_context).tap {|r| r.version(package_version) if package_version } } 69 | let(:candidate_version) { subject; test_provider.candidate_version } 70 | subject { test_provider.load_current_resource } 71 | 72 | context 'with package_name foo' do 73 | let(:package_name) { 'foo' } 74 | before do 75 | stub_cmd(%w{-m pip.__main__ list}, environment: {'PIP_FORMAT' => 'json'}, stdout: '') 76 | stub_cmd(%w{- foo}, input: kind_of(String), stdout: '{"foo":"1.0.0"}') 77 | end 78 | 79 | its(:version) { is_expected.to be nil } 80 | it { expect(candidate_version).to eq '1.0.0' } 81 | end # /context with package_name foo 82 | 83 | context 'with package_name ["foo", "bar"]' do 84 | let(:package_name) { %w{foo bar} } 85 | before do 86 | stub_cmd(%w{-m pip.__main__ list}, environment: {'PIP_FORMAT' => 'json'}, stdout: '') 87 | stub_cmd(%w{- foo bar}, input: kind_of(String), stdout: '{"foo":"1.0.0","bar":"2.0.0"}') 88 | end 89 | 90 | its(:version) { is_expected.to eq [nil, nil] } 91 | it { expect(candidate_version).to eq %w{1.0.0 2.0.0} } 92 | end # /context with package_name ["foo", "bar"] 93 | 94 | context 'with a package with extras' do 95 | let(:package_name) { 'foo[bar]' } 96 | before do 97 | stub_cmd(%w{-m pip.__main__ list}, environment: {'PIP_FORMAT' => 'json'}, stdout: '') 98 | stub_cmd(%w{- foo}, input: kind_of(String), stdout: '{"foo":"1.0.0"}') 99 | end 100 | 101 | its(:version) { is_expected.to be nil } 102 | it { expect(candidate_version).to eq '1.0.0' } 103 | end # /context with a package with extras 104 | 105 | context 'with a package with underscores' do 106 | let(:package_name) { 'cx_foo' } 107 | before do 108 | stub_cmd(%w{-m pip.__main__ list}, environment: {'PIP_FORMAT' => 'json'}, stdout: '') 109 | stub_cmd(%w{- cx-foo}, input: kind_of(String), stdout: '{"cx-foo":"1.0.0"}') 110 | end 111 | 112 | its(:version) { is_expected.to be nil } 113 | it { expect(candidate_version).to eq '1.0.0' } 114 | end # /context with a package with underscores 115 | 116 | context 'with options' do 117 | let(:package_name) { 'foo' } 118 | before do 119 | test_resource.options('--index-url=http://example') 120 | stub_cmd("-m pip.__main__ list --index-url=http://example ", environment: {'PIP_FORMAT' => 'json'}, stdout: '', array_13: true) 121 | stub_cmd("- --index-url=http://example foo", input: kind_of(String), stdout: '{"foo":"1.0.0"}', array_13: true) 122 | end 123 | 124 | its(:version) { is_expected.to be nil } 125 | it { expect(candidate_version).to eq '1.0.0' } 126 | end # /context with options 127 | 128 | context 'with list options' do 129 | let(:package_name) { 'foo' } 130 | before do 131 | test_resource.list_options('--index-url=http://example') 132 | stub_cmd("-m pip.__main__ list --index-url=http://example ", environment: {'PIP_FORMAT' => 'json'}, stdout: '') 133 | stub_cmd("- --index-url=http://example foo", input: kind_of(String), stdout: '{"foo":"1.0.0"}') 134 | end 135 | 136 | its(:version) { is_expected.to be nil } 137 | it { expect(candidate_version).to eq '1.0.0' } 138 | end # /context with list options 139 | 140 | context 'with array list options' do 141 | let(:package_name) { 'foo' } 142 | before do 143 | test_resource.list_options(%w{--index-url=http://example}) 144 | stub_cmd(%w{-m pip.__main__ list --index-url=http://example}, environment: {'PIP_FORMAT' => 'json'}, stdout: '') 145 | stub_cmd(%w{- --index-url=http://example foo}, input: kind_of(String), stdout: '{"foo":"1.0.0"}') 146 | end 147 | 148 | its(:version) { is_expected.to be nil } 149 | it { expect(candidate_version).to eq '1.0.0' } 150 | end # /context with array list options 151 | end # /describe #load_current_resource 152 | 153 | describe 'actions' do 154 | let(:package_name) { nil } 155 | let(:current_version) { nil } 156 | let(:candidate_version) { nil } 157 | let(:test_resource) { PoisePython::Resources::PythonPackage::Resource.new(package_name, chef_run.run_context) } 158 | subject { test_provider.run_action } 159 | before do 160 | current_version = self.current_version 161 | candidate_version = self.candidate_version 162 | allow(test_provider).to receive(:load_current_resource) do 163 | current_resource = double('current_resource', package_name: package_name, version: current_version) 164 | test_provider.instance_eval do 165 | @current_resource = current_resource 166 | @candidate_version = candidate_version 167 | end 168 | end 169 | end 170 | 171 | describe 'action :install' do 172 | before { test_provider.action = :install } 173 | 174 | context 'with package_name foo' do 175 | let(:package_name) { 'foo' } 176 | let(:candidate_version) { '1.0.0' } 177 | it do 178 | stub_cmd(%w{-m pip.__main__ install foo==1.0.0}) 179 | subject 180 | end 181 | end # /context with package_name foo 182 | 183 | context 'with package_name ["foo", "bar"]' do 184 | let(:package_name) { %w{foo bar} } 185 | let(:candidate_version) { %w{1.0.0 2.0.0} } 186 | it do 187 | stub_cmd(%w{-m pip.__main__ install foo==1.0.0 bar==2.0.0}) 188 | subject 189 | end 190 | end # /context with package_name ["foo", "bar"] 191 | 192 | context 'with options' do 193 | let(:package_name) { 'foo' } 194 | let(:candidate_version) { '1.0.0' } 195 | before { test_resource.options('--editable') } 196 | it do 197 | stub_cmd('-m pip.__main__ install --editable foo\\=\\=1.0.0', array_13: true) 198 | subject 199 | end 200 | end # /context with options 201 | 202 | context 'with install options' do 203 | let(:package_name) { 'foo' } 204 | let(:candidate_version) { '1.0.0' } 205 | before { test_resource.install_options('--editable') } 206 | it do 207 | stub_cmd('-m pip.__main__ install --editable foo\\=\\=1.0.0') 208 | subject 209 | end 210 | end # /context with install options 211 | 212 | context 'with array install options' do 213 | let(:package_name) { 'foo' } 214 | let(:candidate_version) { '1.0.0' } 215 | before { test_resource.install_options(%w{--editable}) } 216 | it do 217 | stub_cmd(%w{-m pip.__main__ install --editable foo==1.0.0}) 218 | subject 219 | end 220 | end # /context with array install options 221 | 222 | context 'with a package with extras' do 223 | let(:package_name) { 'foo[bar]' } 224 | let(:candidate_version) { '1.0.0' } 225 | it do 226 | stub_cmd(%w{-m pip.__main__ install foo[bar]==1.0.0}) 227 | subject 228 | end 229 | end # /context with a package with extras 230 | 231 | context 'with a package with underscores' do 232 | let(:package_name) { 'cx_foo' } 233 | let(:candidate_version) { '1.0.0' } 234 | it do 235 | stub_cmd(%w{-m pip.__main__ install cx_foo==1.0.0}) 236 | subject 237 | end 238 | end # /context with a package with underscores 239 | end # /describe action :install 240 | 241 | describe 'action :upgrade' do 242 | before { test_provider.action = :upgrade } 243 | 244 | context 'with package_name foo' do 245 | let(:package_name) { 'foo' } 246 | let(:candidate_version) { '1.0.0' } 247 | it do 248 | stub_cmd(%w{-m pip.__main__ install --upgrade foo==1.0.0}) 249 | subject 250 | end 251 | end # /context with package_name foo 252 | 253 | context 'with package_name ["foo", "bar"]' do 254 | let(:package_name) { %w{foo bar} } 255 | let(:candidate_version) { %w{1.0.0 2.0.0} } 256 | it do 257 | stub_cmd(%w{-m pip.__main__ install --upgrade foo==1.0.0 bar==2.0.0}) 258 | subject 259 | end 260 | end # /context with package_name ["foo", "bar"] 261 | end # /describe action :upgrade 262 | 263 | describe 'action :remove' do 264 | before { test_provider.action = :remove } 265 | 266 | context 'with package_name foo' do 267 | let(:package_name) { 'foo' } 268 | let(:current_version) { '1.0.0' } 269 | it do 270 | stub_cmd(%w{-m pip.__main__ uninstall --yes foo}) 271 | subject 272 | end 273 | end # /context with package_name foo 274 | 275 | context 'with package_name ["foo", "bar"]' do 276 | let(:package_name) { %w{foo bar} } 277 | let(:current_version) { %w{1.0.0 2.0.0} } 278 | it do 279 | stub_cmd(%w{-m pip.__main__ uninstall --yes foo bar}) 280 | subject 281 | end 282 | end # /context with package_name ["foo", "bar"] 283 | end # /describe action :remove 284 | end # /describe actions 285 | 286 | describe '#parse_pip_list' do 287 | let(:text) { '' } 288 | subject { test_provider.send(:parse_pip_list, text) } 289 | 290 | context 'with no content' do 291 | it { is_expected.to eq({}) } 292 | end # /context with no content 293 | 294 | context 'with standard content' do 295 | let(:text) { <<-EOH } 296 | eventlet (0.12.1) 297 | Fabric (1.9.1) 298 | fabric-rundeck (1.2, /Users/coderanger/src/bal/fabric-rundeck) 299 | flake8 (2.1.0.dev0) 300 | cx-Freeze (4.3.4) 301 | EOH 302 | it { is_expected.to eq({'eventlet' => '0.12.1', 'fabric' => '1.9.1', 'fabric-rundeck' => '1.2', 'flake8' => '2.1.0.dev0', 'cx-freeze' => '4.3.4'}) } 303 | end # /context with standard content 304 | 305 | context 'with JSON content' do 306 | let(:text) { <<-EOH.strip } 307 | [{"name":"eventlet","version":"0.12.1"}, {"name":"Fabric","version":"1.9.1"}, {"name":"fabric-rundeck","version":"1.2"}, {"name":"flake8","version":"2.1.0.dev0"}, {"name":"cx-Freeze","version":"4.3.4"}] 308 | EOH 309 | it { is_expected.to eq({'eventlet' => '0.12.1', 'fabric' => '1.9.1', 'fabric-rundeck' => '1.2', 'flake8' => '2.1.0.dev0', 'cx-freeze' => '4.3.4'}) } 310 | end # /context with JSON content 311 | 312 | context 'with malformed content' do 313 | let(:text) { <<-EOH } 314 | eventlet (0.12.1) 315 | Fabric (1.9.1) 316 | fabric-rundeck (1.2, /Users/coderanger/src/bal/fabric-rundeck) 317 | flake 8 (2.1.0.dev0) 318 | cx_Freeze (4.3.4) 319 | EOH 320 | it { is_expected.to eq({'eventlet' => '0.12.1', 'fabric' => '1.9.1', 'fabric-rundeck' => '1.2', 'cx-freeze' => '4.3.4'}) } 321 | end # /context with malformed content 322 | end # /describe #parse_pip_list 323 | end # /describe PoisePython::Resources::PythonPackage::Provider 324 | end 325 | -------------------------------------------------------------------------------- /test/spec/resources/python_runtime_pip_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Resources::PythonRuntimePip do 20 | step_into(:python_runtime_pip) 21 | recipe do 22 | python_runtime 'test' 23 | python_runtime_pip 'test' do 24 | get_pip_url 'http://example.com/' 25 | end 26 | end 27 | before do 28 | provider = PoisePython::Resources::PythonRuntimePip::Provider 29 | allow_any_instance_of(provider).to receive(:pip_version).and_return(nil) 30 | allow_any_instance_of(provider).to receive(:bootstrap_pip) 31 | end 32 | 33 | # Make sure it can at least vaguely run. 34 | it { chef_run } 35 | end 36 | -------------------------------------------------------------------------------- /test/spec/resources/python_runtime_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | class PythonRuntimeTestProvider < PoisePython::PythonProviders::Base 20 | provides(:test) 21 | def self.provides_auto?(*args) 22 | true 23 | end 24 | def python_binary 25 | '/python' 26 | end 27 | def install_python 28 | end 29 | def uninstall_python 30 | end 31 | end 32 | 33 | describe PoisePython::Resources::PythonRuntime do 34 | step_into(:python_runtime) 35 | let(:venv_installed) { false } 36 | before do 37 | allow_any_instance_of(PythonRuntimeTestProvider).to receive(:poise_shell_out).with(%w{/python -m venv -h}, environment: {}).and_return(double(error?: !venv_installed)) 38 | end 39 | 40 | context 'with defaults' do 41 | recipe do 42 | python_runtime 'test' do 43 | provider :test 44 | end 45 | end 46 | 47 | it { is_expected.to install_python_runtime_pip('test').with(version: nil, get_pip_url: 'https://bootstrap.pypa.io/get-pip.py') } 48 | end # /context with defaults 49 | 50 | context 'with a pip_version' do 51 | recipe do 52 | python_runtime 'test' do 53 | provider :test 54 | pip_version '1.2.3' 55 | end 56 | end 57 | 58 | it { is_expected.to install_python_runtime_pip('test').with(version: '1.2.3', get_pip_url: 'https://bootstrap.pypa.io/get-pip.py') } 59 | end # /context with a pip_version 60 | 61 | context 'with a pip_version URL' do 62 | recipe do 63 | python_runtime 'test' do 64 | provider :test 65 | pip_version 'http://example.com/get-pip.py' 66 | end 67 | end 68 | 69 | it { is_expected.to install_python_runtime_pip('test').with(version: nil, get_pip_url: 'http://example.com/get-pip.py') } 70 | end # /context with a pip_version URL 71 | 72 | context 'with a pip_version and a get_pip_url' do 73 | recipe do 74 | python_runtime 'test' do 75 | provider :test 76 | pip_version '1.2.3' 77 | get_pip_url 'http://example.com/get-pip.py' 78 | end 79 | end 80 | 81 | it { is_expected.to install_python_runtime_pip('test').with(version: '1.2.3', get_pip_url: 'http://example.com/get-pip.py') } 82 | end # /context with a pip_version and a get_pip_url 83 | end 84 | -------------------------------------------------------------------------------- /test/spec/resources/python_virtualenv_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Resources::PythonVirtualenv do 20 | step_into(:python_virtualenv) 21 | let(:expect_cmd) { nil } 22 | let(:has_venv) { false } 23 | let(:venv_help_output) { ' --without-pip Skips installing or upgrading pip in the virtual' } 24 | let(:expect_user) { nil } 25 | before do 26 | if expect_cmd 27 | expect_any_instance_of(PoisePython::Resources::PythonVirtualenv::Provider).to receive(:python_shell_out).with(%w{-m venv -h}).and_return(double(error?: !has_venv, stdout: venv_help_output)) 28 | expect_any_instance_of(PoisePython::Resources::PythonVirtualenv::Provider).to receive(:python_shell_out!).with(expect_cmd, environment: be_a(Hash), user: expect_user, group: expect_user) 29 | end 30 | end 31 | 32 | context 'without venv' do 33 | let(:expect_cmd) { %w{-m virtualenv /test} } 34 | recipe do 35 | python_virtualenv '/test' 36 | end 37 | 38 | it { is_expected.to create_python_virtualenv('/test') } 39 | it { expect(chef_run.python_virtualenv('/test').python_binary).to eq '/test/bin/python' } 40 | it { expect(chef_run.python_virtualenv('/test').python_environment).to eq({}) } 41 | it { is_expected.to install_python_package('wheel').with(parent_python: chef_run.python_virtualenv('/test')) } 42 | it { is_expected.to install_python_package('setuptools').with(parent_python: chef_run.python_virtualenv('/test')) } 43 | it { is_expected.to_not install_python_package('virtualenv') } 44 | it { is_expected.to install_python_runtime_pip('/test').with(parent: chef_run.python_virtualenv('/test')) } 45 | end # /context without venv 46 | 47 | context 'with venv' do 48 | let(:has_venv) { true } 49 | let(:expect_cmd) { %w{-m venv --without-pip /test} } 50 | recipe do 51 | python_virtualenv '/test' 52 | end 53 | 54 | it { is_expected.to create_python_virtualenv('/test') } 55 | end # /context with venv 56 | 57 | context 'with venv on python 3.3' do 58 | let(:has_venv) { true } 59 | let(:venv_help_output) { '' } 60 | let(:expect_cmd) { %w{-m venv /test} } 61 | recipe do 62 | python_virtualenv '/test' 63 | end 64 | 65 | it { is_expected.to create_python_virtualenv('/test') } 66 | end # /context with venv on python 3.3 67 | 68 | context 'with system_site_packages' do 69 | let(:expect_cmd) { %w{-m virtualenv --system-site-packages /test} } 70 | recipe do 71 | python_virtualenv '/test' do 72 | system_site_packages true 73 | end 74 | end 75 | 76 | it { is_expected.to create_python_virtualenv('/test') } 77 | end # /context with system_site_packages 78 | 79 | context 'with a user and group' do 80 | let(:expect_cmd) { %w{-m virtualenv /test} } 81 | let(:expect_user) { 'me' } 82 | recipe do 83 | python_virtualenv '/test' do 84 | user 'me' 85 | group 'me' 86 | end 87 | end 88 | 89 | it { is_expected.to create_python_virtualenv('/test') } 90 | it { is_expected.to install_python_package('wheel').with(group: 'me', user: 'me') } 91 | it { is_expected.to install_python_package('setuptools').with(group: 'me', user: 'me') } 92 | end # /context with a user and group 93 | 94 | 95 | context 'with action :delete' do 96 | recipe do 97 | python_virtualenv '/test' do 98 | action :delete 99 | end 100 | end 101 | 102 | it { is_expected.to delete_python_virtualenv('/test') } 103 | it { is_expected.to delete_directory('/test') } 104 | end # /context with action :delete 105 | 106 | context 'with a parent Python' do 107 | let(:expect_cmd) { %w{-m virtualenv /test} } 108 | recipe do 109 | python_runtime '2' do 110 | def self.python_environment 111 | {'KEY' => 'VALUE'} 112 | end 113 | end 114 | python_virtualenv '/test' 115 | end 116 | 117 | it { is_expected.to create_python_virtualenv('/test') } 118 | it { expect(chef_run.python_virtualenv('/test').python_binary).to eq '/test/bin/python' } 119 | it { expect(chef_run.python_virtualenv('/test').python_environment).to eq({'KEY' => 'VALUE'}) } 120 | end # /context with a parent Python 121 | end 122 | -------------------------------------------------------------------------------- /test/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'poise_boiler/spec_helper' 18 | require 'poise_python' 19 | require 'poise_python/cheftie' 20 | -------------------------------------------------------------------------------- /test/spec/utils/python_encoder_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Utils::PythonEncoder do 20 | let(:obj) { nil } 21 | subject { described_class.new(obj).encode } 22 | 23 | context 'with a string' do 24 | let(:obj) { 'foobar' } 25 | it { is_expected.to eq '"foobar"' } 26 | end # /context with a string 27 | 28 | context 'with a complicated string' do 29 | let(:obj) { "im\nalittle\"teapot'" } 30 | it { is_expected.to eq '"im\\nalittle\\"teapot\'"' } 31 | end # /context with a complicated string 32 | 33 | context 'with an integer' do 34 | let(:obj) { 123 } 35 | it { is_expected.to eq '123' } 36 | end # /context with an integer 37 | 38 | context 'with a float' do 39 | let(:obj) { 1.3 } 40 | it { is_expected.to eq '1.3' } 41 | end # /context with a float 42 | 43 | context 'with a hash' do 44 | let(:obj) { {foo: 'bar'} } 45 | it { is_expected.to eq '{"foo":"bar"}' } 46 | end # /context with a hash 47 | 48 | context 'with an array' do 49 | let(:obj) { ['foo', 1, 'bar'] } 50 | it { is_expected.to eq '["foo",1,"bar"]' } 51 | end # /context with an array 52 | 53 | context 'with true' do 54 | let(:obj) { true } 55 | it { is_expected.to eq 'True' } 56 | end # /context with true 57 | 58 | context 'with false' do 59 | let(:obj) { false } 60 | it { is_expected.to eq 'False' } 61 | end # /context with false 62 | 63 | context 'with nil' do 64 | let(:obj) { nil } 65 | it { is_expected.to eq 'None' } 66 | end # /context with nil 67 | 68 | context 'with a broken object' do 69 | let(:obj) do 70 | {}.tap {|obj| obj[:x] = obj } 71 | end 72 | it { expect { subject }.to raise_error ArgumentError } 73 | end # /context with a broken object 74 | 75 | context 'with a complex object' do 76 | let(:obj) { {a: [1, "2", true], b: false, c: {d: nil}} } 77 | it { is_expected.to eq '{"a":[1,"2",True],"b":False,"c":{"d":None}}' } 78 | end # /context with a complex object 79 | end 80 | -------------------------------------------------------------------------------- /test/spec/utils_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2017, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'spec_helper' 18 | 19 | describe PoisePython::Utils do 20 | describe '.to_python' do 21 | subject { described_class.to_python(1) } 22 | it { is_expected.to eq '1' } 23 | # More detailed encoder specs in python_encoder_spec.rb 24 | end # /describe .to_python 25 | 26 | describe '.path_to_module' do 27 | let(:path) { '' } 28 | let(:base) { nil } 29 | subject { described_class.path_to_module(path, base) } 30 | 31 | context 'with a relative path' do 32 | let(:path) { 'foo.py' } 33 | it { is_expected.to eq 'foo' } 34 | end # /context with a relative path 35 | 36 | context 'with a nested relative path' do 37 | let(:path) { File.join('foo', 'bar', 'baz.py') } 38 | it { is_expected.to eq 'foo.bar.baz' } 39 | end # /context with a nested relative path 40 | 41 | context 'with a non-.py file' do 42 | let(:path) { File.join('foo', 'bar', 'baz') } 43 | it { is_expected.to eq 'foo.bar.baz' } 44 | end # /context with a non-.py file 45 | 46 | context 'with a base path' do 47 | let(:path) { File.join('', 'foo', 'bar', 'baz.py') } 48 | let(:base) { File.join('', 'foo') } 49 | it { is_expected.to eq 'bar.baz' } 50 | end # /context with a base path 51 | 52 | context 'with a base path that does not match the path' do 53 | let(:path) { File.join('', 'foo', 'bar', 'baz.py') } 54 | let(:base) { File.join('', 'bar') } 55 | it { expect { subject }.to raise_error PoisePython::Error } 56 | end # /context with a base path that does not match the path 57 | 58 | context 'with a base and relative path' do 59 | let(:path) { File.join('bar', 'baz.py') } 60 | let(:base) { File.join('', 'foo') } 61 | it { is_expected.to eq 'bar.baz' } 62 | end # /context with a base and relative path 63 | end # /describe .path_to_module 64 | 65 | describe '.module_to_path' do 66 | let(:mod) { '' } 67 | let(:base) { nil } 68 | subject { described_class.module_to_path(mod, base) } 69 | 70 | context 'with a module' do 71 | let(:mod) { 'foo' } 72 | it { is_expected.to eq 'foo.py' } 73 | end # /context with a module 74 | 75 | context 'with a nested module' do 76 | let(:mod) { 'foo.bar.baz' } 77 | it { is_expected.to eq File.join('foo', 'bar', 'baz.py') } 78 | end # /context with a nested module 79 | 80 | context 'with a base path' do 81 | let(:mod) { 'bar.baz' } 82 | let(:base) { File.join('', 'foo') } 83 | it { is_expected.to eq File.join('', 'foo', 'bar', 'baz.py') } 84 | end # /context with a base path 85 | end # /describe .module_to_path 86 | end 87 | --------------------------------------------------------------------------------