poise-python 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.kitchen.travis.yml +9 -0
- data/.kitchen.yml +8 -0
- data/.travis.yml +21 -0
- data/.yardopts +7 -0
- data/Berksfile +28 -0
- data/Gemfile +33 -0
- data/LICENSE +201 -0
- data/README.md +399 -0
- data/Rakefile +17 -0
- data/chef/attributes/default.rb +24 -0
- data/chef/recipes/default.rb +20 -0
- data/lib/poise_python.rb +25 -0
- data/lib/poise_python/cheftie.rb +18 -0
- data/lib/poise_python/error.rb +23 -0
- data/lib/poise_python/python_command_mixin.rb +45 -0
- data/lib/poise_python/python_providers.rb +35 -0
- data/lib/poise_python/python_providers/base.rb +177 -0
- data/lib/poise_python/python_providers/portable_pypy.rb +96 -0
- data/lib/poise_python/python_providers/scl.rb +77 -0
- data/lib/poise_python/python_providers/system.rb +86 -0
- data/lib/poise_python/resources.rb +31 -0
- data/lib/poise_python/resources/pip_requirements.rb +102 -0
- data/lib/poise_python/resources/python_execute.rb +83 -0
- data/lib/poise_python/resources/python_package.rb +322 -0
- data/lib/poise_python/resources/python_runtime.rb +114 -0
- data/lib/poise_python/resources/python_runtime_pip.rb +167 -0
- data/lib/poise_python/resources/python_runtime_test.rb +185 -0
- data/lib/poise_python/resources/python_virtualenv.rb +164 -0
- data/lib/poise_python/utils.rb +63 -0
- data/lib/poise_python/utils/python_encoder.rb +73 -0
- data/lib/poise_python/version.rb +20 -0
- data/poise-python.gemspec +41 -0
- data/test/cookbooks/poise-python_test/metadata.rb +18 -0
- data/test/cookbooks/poise-python_test/recipes/default.rb +40 -0
- data/test/gemfiles/chef-12.gemfile +19 -0
- data/test/gemfiles/master.gemfile +23 -0
- data/test/integration/default/serverspec/default_spec.rb +102 -0
- data/test/spec/python_command_mixin_spec.rb +115 -0
- data/test/spec/python_providers/portable_pypy_spec.rb +68 -0
- data/test/spec/python_providers/scl_spec.rb +75 -0
- data/test/spec/python_providers/system_spec.rb +81 -0
- data/test/spec/resources/pip_requirements_spec.rb +69 -0
- data/test/spec/resources/python_package_spec.rb +65 -0
- data/test/spec/resources/python_runtime_pip_spec.rb +33 -0
- data/test/spec/resources/python_virtualenv_spec.rb +103 -0
- data/test/spec/spec_helper.rb +19 -0
- data/test/spec/utils/python_encoder_spec.rb +79 -0
- data/test/spec/utils_spec.rb +86 -0
- metadata +170 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, 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 pip_version
|
50
|
+
# Version of pip to install. If set to `true`, the latest available
|
51
|
+
# pip will be used. If set to `false`, pip will not be installed. If
|
52
|
+
# set to a URL, that will be used as the URL to get-pip.py. If a
|
53
|
+
# non-URL version is given, the get-pip.py installer will be
|
54
|
+
# downloaded from the internet.
|
55
|
+
# @note Due to https://github.com/pypa/pip/issues/1087, the latest
|
56
|
+
# version of pip will always be installed initially. It will then
|
57
|
+
# downgrade to the requested version if needed.
|
58
|
+
# @note Disabling the pip install may result in other resources being
|
59
|
+
# non-functional.
|
60
|
+
# @return [String, Boolean]
|
61
|
+
# @example Install from a locally-hosted copy of get-pip.py
|
62
|
+
# python_runtime '2' do
|
63
|
+
# pip_version 'http://myserver/get-pip.py'
|
64
|
+
# end
|
65
|
+
attribute(:pip_version, kind_of: [String, TrueClass, FalseClass], default: true)
|
66
|
+
# @!attribute setuptools_version
|
67
|
+
# Version of Setuptools to install. It set to `true`, the latest
|
68
|
+
# available version will be used. If set to `false`, setuptools will
|
69
|
+
# not be installed.
|
70
|
+
# @return [String, Boolean]
|
71
|
+
attribute(:setuptools_version, kind_of: [String, TrueClass, FalseClass], default: true)
|
72
|
+
# @!attribute virtualenv_version
|
73
|
+
# Version of Virtualenv to install. It set to `true`, the latest
|
74
|
+
# available version will be used. If set to `false`, virtualenv will
|
75
|
+
# not be installed. Virtualenv will never be installed if the built-in
|
76
|
+
# venv module is available.
|
77
|
+
# @note Disabling the virtualenv install may result in other resources
|
78
|
+
# being non-functional.
|
79
|
+
# @return [String, Boolean]
|
80
|
+
attribute(:virtualenv_version, kind_of: [String, TrueClass, FalseClass], default: true)
|
81
|
+
# @!attribute wheel_version
|
82
|
+
# Version of Wheel to install. It set to `true`, the latest
|
83
|
+
# available version will be used. If set to `false`, wheel will not
|
84
|
+
# be installed.
|
85
|
+
# @return [String, Boolean]
|
86
|
+
attribute(:wheel_version, kind_of: [String, TrueClass, FalseClass], default: true)
|
87
|
+
|
88
|
+
# The path to the `python` binary for this Python installation. This is
|
89
|
+
# an output property.
|
90
|
+
#
|
91
|
+
# @return [String]
|
92
|
+
# @example
|
93
|
+
# execute "#{resources('python_runtime[2.7]').python_binary} myapp.py"
|
94
|
+
def python_binary
|
95
|
+
provider_for_action(:python_binary).python_binary
|
96
|
+
end
|
97
|
+
|
98
|
+
# The environment variables for this Python installation. This is an
|
99
|
+
# output property.
|
100
|
+
#
|
101
|
+
# @return [Hash<String, String>]
|
102
|
+
# @example
|
103
|
+
# execute '/opt/myapp.py' do
|
104
|
+
# environment resources('python_runtime[2.7]').python_environment
|
105
|
+
# end
|
106
|
+
def python_environment
|
107
|
+
provider_for_action(:python_environment).python_environment
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Providers can be found under lib/poise_python/python_providers/
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, 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 'tempfile'
|
18
|
+
|
19
|
+
require 'chef/resource'
|
20
|
+
require 'poise'
|
21
|
+
|
22
|
+
|
23
|
+
module PoisePython
|
24
|
+
module Resources
|
25
|
+
# (see PythonRuntimePip::Resource)
|
26
|
+
# @since 1.0.0
|
27
|
+
# @api private
|
28
|
+
module PythonRuntimePip
|
29
|
+
# URL for the default get-pip.py script.
|
30
|
+
DEFAULT_GET_PIP_URL = 'https://bootstrap.pypa.io/get-pip.py'
|
31
|
+
|
32
|
+
# A `python_runtime_pip` resource to install/upgrade pip itself. This is
|
33
|
+
# used internally by `python_runtime` and is not intended to be a public
|
34
|
+
# API.
|
35
|
+
#
|
36
|
+
# @provides python_runtime_pip
|
37
|
+
# @action install
|
38
|
+
# @action uninstall
|
39
|
+
class Resource < Chef::Resource
|
40
|
+
include Poise(parent: :python_runtime)
|
41
|
+
provides(:python_runtime_pip)
|
42
|
+
actions(:install, :uninstall)
|
43
|
+
|
44
|
+
# @!attribute version
|
45
|
+
# Version of pip to install. Only kind of works due to
|
46
|
+
# https://github.com/pypa/pip/issues/1087.
|
47
|
+
# @return [String]
|
48
|
+
attribute(:version, kind_of: String)
|
49
|
+
# @!attribute get_pip_url
|
50
|
+
# URL to the get-pip.py script. Defaults to pulling it from pypa.io.
|
51
|
+
# @return [String]
|
52
|
+
attribute(:get_pip_url, kind_of: String, default: DEFAULT_GET_PIP_URL)
|
53
|
+
end
|
54
|
+
|
55
|
+
# The default provider for `python_runtime_pip`.
|
56
|
+
#
|
57
|
+
# @see Resource
|
58
|
+
# @provides python_runtime_pip
|
59
|
+
class Provider < Chef::Provider
|
60
|
+
include Poise
|
61
|
+
provides(:python_runtime_pip)
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def load_current_resource
|
65
|
+
super.tap do |current_resource|
|
66
|
+
# Try to find the current version if possible.
|
67
|
+
current_resource.version(pip_version)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# The `install` action for the `python_runtime_pip` resource.
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
def action_install
|
75
|
+
if current_resource.version
|
76
|
+
install_pip
|
77
|
+
else
|
78
|
+
bootstrap_pip
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# The `uninstall` action for the `python_runtime_pip` resource.
|
83
|
+
#
|
84
|
+
# @return [void]
|
85
|
+
def action_uninstall
|
86
|
+
notifying_block do
|
87
|
+
python_package 'pip' do
|
88
|
+
action :uninstall
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Bootstrap pip using get-pip.py.
|
96
|
+
#
|
97
|
+
# @return [void]
|
98
|
+
def bootstrap_pip
|
99
|
+
# Always updated if we have hit this point.
|
100
|
+
new_resource.updated_by_last_action(true)
|
101
|
+
# Pending https://github.com/pypa/pip/issues/1087.
|
102
|
+
if new_resource.version
|
103
|
+
Chef::Log.warn("pip does not support bootstrapping a specific version, see https://github.com/pypa/pip/issues/1087.")
|
104
|
+
end
|
105
|
+
# Use a temp file to hold the installer.
|
106
|
+
Tempfile.create(['get-pip', '.py']) do |temp|
|
107
|
+
# Download the get-pip.py.
|
108
|
+
get_pip = Chef::HTTP.new(new_resource.get_pip_url).get('')
|
109
|
+
# Write it to the temp file.
|
110
|
+
temp.write(get_pip)
|
111
|
+
# Close the file to flush it.
|
112
|
+
temp.close
|
113
|
+
# Run the install. This probably needs some handling for proxies et
|
114
|
+
# al. Disable setuptools and wheel as we will install those later.
|
115
|
+
# Use the environment vars instead of CLI arguments so I don't have
|
116
|
+
# to deal with bootstrap versions that don't support --no-wheel.
|
117
|
+
shell_out!([new_resource.parent.python_binary, temp.path], environment: new_resource.parent.python_environment.merge('PIP_NO_SETUPTOOLS' => '1', 'PIP_NO_WHEEL' => '1'))
|
118
|
+
end
|
119
|
+
new_pip_version = pip_version
|
120
|
+
if new_resource.version && new_pip_version != new_resource.version
|
121
|
+
# We probably want to downgrade, which is silly but ¯\_(ツ)_/¯.
|
122
|
+
# Can be removed once https://github.com/pypa/pip/issues/1087 is fixed.
|
123
|
+
current_resource.version(new_pip_version)
|
124
|
+
install_pip
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Upgrade (or downgrade) pip using itself. Should work back at least
|
129
|
+
# pip 1.5.
|
130
|
+
#
|
131
|
+
# @return [void]
|
132
|
+
def install_pip
|
133
|
+
if new_resource.version
|
134
|
+
# Already up to date, we're done here.
|
135
|
+
return if current_resource.version == new_resource.version
|
136
|
+
else
|
137
|
+
# We don't wany a specific version, so just make a general check.
|
138
|
+
return if current_resource.version
|
139
|
+
end
|
140
|
+
|
141
|
+
notifying_block do
|
142
|
+
# Use pip to upgrade (or downgrade) itself.
|
143
|
+
python_package 'pip' do
|
144
|
+
action :upgrade
|
145
|
+
version new_resource.version if new_resource.version
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Find the version of pip currently installed in this Python runtime.
|
151
|
+
# Returns nil if not installed.
|
152
|
+
#
|
153
|
+
# @return [String, nil]
|
154
|
+
def pip_version
|
155
|
+
cmd = shell_out([new_resource.parent.python_binary, '-m', 'pip.__main__', '--version'], environment: new_resource.parent.python_environment)
|
156
|
+
if cmd.error?
|
157
|
+
# Not installed, probably.
|
158
|
+
nil
|
159
|
+
else
|
160
|
+
cmd.stdout[/pip ([\d.a-z]+)/, 1]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, 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('', 'root', "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
|
62
|
+
|
63
|
+
# Install and log the version.
|
64
|
+
python_runtime new_resource.name do
|
65
|
+
provider new_resource.runtime_provider if new_resource.runtime_provider
|
66
|
+
version new_resource.version
|
67
|
+
end
|
68
|
+
test_version
|
69
|
+
|
70
|
+
# Test python_package.
|
71
|
+
python_package 'sqlparse remove before' do
|
72
|
+
action :remove
|
73
|
+
package_name 'sqlparse'
|
74
|
+
python new_resource.name
|
75
|
+
end
|
76
|
+
test_import('sqlparse', 'sqlparse_before')
|
77
|
+
python_package 'sqlparse' do
|
78
|
+
python new_resource.name
|
79
|
+
notifies :create, sentinel_file('sqlparse'), :immediately
|
80
|
+
end
|
81
|
+
test_import('sqlparse', 'sqlparse_mid')
|
82
|
+
python_package 'sqlparse again' do
|
83
|
+
package_name 'sqlparse'
|
84
|
+
python new_resource.name
|
85
|
+
notifies :create, sentinel_file('sqlparse2'), :immediately
|
86
|
+
end
|
87
|
+
python_package 'sqlparse remove after' do
|
88
|
+
action :remove
|
89
|
+
package_name 'sqlparse'
|
90
|
+
python new_resource.name
|
91
|
+
end
|
92
|
+
test_import('sqlparse', 'sqlparse_after')
|
93
|
+
|
94
|
+
# Use setuptools to test something that should always be installed.
|
95
|
+
python_package 'setuptools' do
|
96
|
+
python new_resource.name
|
97
|
+
notifies :create, sentinel_file('setuptools'), :immediately
|
98
|
+
end
|
99
|
+
|
100
|
+
# Multi-package install.
|
101
|
+
python_package ['pep8', 'pytz'] do
|
102
|
+
python new_resource.name
|
103
|
+
end
|
104
|
+
test_import('pep8')
|
105
|
+
test_import('pytz')
|
106
|
+
|
107
|
+
# Create a virtualenv.
|
108
|
+
python_virtualenv ::File.join(new_resource.path, 'venv') do
|
109
|
+
python new_resource.name
|
110
|
+
end
|
111
|
+
|
112
|
+
# Install a package inside a virtualenv.
|
113
|
+
python_package 'Pytest' do
|
114
|
+
virtualenv ::File.join(new_resource.path, 'venv')
|
115
|
+
end
|
116
|
+
test_import('pytest')
|
117
|
+
test_import('pytest', 'pytest_venv', python: nil, virtualenv: ::File.join(new_resource.path, 'venv'))
|
118
|
+
|
119
|
+
# Create and install a requirements file.
|
120
|
+
file ::File.join(new_resource.path, 'requirements.txt') do
|
121
|
+
content <<-EOH
|
122
|
+
requests==2.7.0
|
123
|
+
six==1.8.0
|
124
|
+
EOH
|
125
|
+
end
|
126
|
+
pip_requirements ::File.join(new_resource.path, 'requirements.txt') do
|
127
|
+
python new_resource.name
|
128
|
+
end
|
129
|
+
test_import('requests')
|
130
|
+
test_import('six')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def sentinel_file(name)
|
135
|
+
file ::File.join(new_resource.path, "sentinel_#{name}") do
|
136
|
+
action :nothing
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def test_version(python: new_resource.name, virtualenv: nil)
|
143
|
+
# Only queue up this resource once, the ivar is just for tracking.
|
144
|
+
@python_version_test ||= file ::File.join(new_resource.path, 'python_version.py') do
|
145
|
+
user 'root'
|
146
|
+
group 'root'
|
147
|
+
mode '644'
|
148
|
+
content <<-EOH
|
149
|
+
import sys, platform
|
150
|
+
open(sys.argv[1], 'w').write(platform.python_version())
|
151
|
+
EOH
|
152
|
+
end
|
153
|
+
|
154
|
+
python_execute "#{@python_version_test.path} #{::File.join(new_resource.path, 'version')}" do
|
155
|
+
python python if python
|
156
|
+
virtualenv virtualenv if virtualenv
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_import(name, path=name, python: new_resource.name, virtualenv: nil)
|
161
|
+
# Only queue up this resource once, the ivar is just for tracking.
|
162
|
+
@python_import_test ||= file ::File.join(new_resource.path, 'import_version.py') do
|
163
|
+
user 'root'
|
164
|
+
group 'root'
|
165
|
+
mode '644'
|
166
|
+
content <<-EOH
|
167
|
+
try:
|
168
|
+
import sys
|
169
|
+
mod = __import__(sys.argv[1])
|
170
|
+
open(sys.argv[2], 'w').write(mod.__version__)
|
171
|
+
except ImportError:
|
172
|
+
pass
|
173
|
+
EOH
|
174
|
+
end
|
175
|
+
|
176
|
+
python_execute "#{@python_import_test.path} #{name} #{::File.join(new_resource.path, "import_#{path}")}" do
|
177
|
+
python python if python
|
178
|
+
virtualenv virtualenv if virtualenv
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|