poise-python 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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