poise-python 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.kitchen.travis.yml +9 -0
  4. data/.kitchen.yml +8 -0
  5. data/.travis.yml +21 -0
  6. data/.yardopts +7 -0
  7. data/Berksfile +28 -0
  8. data/Gemfile +33 -0
  9. data/LICENSE +201 -0
  10. data/README.md +399 -0
  11. data/Rakefile +17 -0
  12. data/chef/attributes/default.rb +24 -0
  13. data/chef/recipes/default.rb +20 -0
  14. data/lib/poise_python.rb +25 -0
  15. data/lib/poise_python/cheftie.rb +18 -0
  16. data/lib/poise_python/error.rb +23 -0
  17. data/lib/poise_python/python_command_mixin.rb +45 -0
  18. data/lib/poise_python/python_providers.rb +35 -0
  19. data/lib/poise_python/python_providers/base.rb +177 -0
  20. data/lib/poise_python/python_providers/portable_pypy.rb +96 -0
  21. data/lib/poise_python/python_providers/scl.rb +77 -0
  22. data/lib/poise_python/python_providers/system.rb +86 -0
  23. data/lib/poise_python/resources.rb +31 -0
  24. data/lib/poise_python/resources/pip_requirements.rb +102 -0
  25. data/lib/poise_python/resources/python_execute.rb +83 -0
  26. data/lib/poise_python/resources/python_package.rb +322 -0
  27. data/lib/poise_python/resources/python_runtime.rb +114 -0
  28. data/lib/poise_python/resources/python_runtime_pip.rb +167 -0
  29. data/lib/poise_python/resources/python_runtime_test.rb +185 -0
  30. data/lib/poise_python/resources/python_virtualenv.rb +164 -0
  31. data/lib/poise_python/utils.rb +63 -0
  32. data/lib/poise_python/utils/python_encoder.rb +73 -0
  33. data/lib/poise_python/version.rb +20 -0
  34. data/poise-python.gemspec +41 -0
  35. data/test/cookbooks/poise-python_test/metadata.rb +18 -0
  36. data/test/cookbooks/poise-python_test/recipes/default.rb +40 -0
  37. data/test/gemfiles/chef-12.gemfile +19 -0
  38. data/test/gemfiles/master.gemfile +23 -0
  39. data/test/integration/default/serverspec/default_spec.rb +102 -0
  40. data/test/spec/python_command_mixin_spec.rb +115 -0
  41. data/test/spec/python_providers/portable_pypy_spec.rb +68 -0
  42. data/test/spec/python_providers/scl_spec.rb +75 -0
  43. data/test/spec/python_providers/system_spec.rb +81 -0
  44. data/test/spec/resources/pip_requirements_spec.rb +69 -0
  45. data/test/spec/resources/python_package_spec.rb +65 -0
  46. data/test/spec/resources/python_runtime_pip_spec.rb +33 -0
  47. data/test/spec/resources/python_virtualenv_spec.rb +103 -0
  48. data/test/spec/spec_helper.rb +19 -0
  49. data/test/spec/utils/python_encoder_spec.rb +79 -0
  50. data/test/spec/utils_spec.rb +86 -0
  51. 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