capsulecd 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.dockerignore +5 -0
  4. data/.gitignore +95 -0
  5. data/.rspec +3 -0
  6. data/.simplecov +9 -0
  7. data/Dockerfile +16 -0
  8. data/Dockerfile.chef +26 -0
  9. data/Dockerfile.javascript +7 -0
  10. data/Dockerfile.node +7 -0
  11. data/Dockerfile.python +7 -0
  12. data/Dockerfile.ruby +4 -0
  13. data/FEATURES.md +12 -0
  14. data/Gemfile +26 -0
  15. data/LICENSE.md +22 -0
  16. data/README.md +227 -0
  17. data/Rakefile +43 -0
  18. data/bin/capsulecd +4 -0
  19. data/capsulecd.gemspec +27 -0
  20. data/circle.yml +24 -0
  21. data/lib/capsulecd/base/common/git_utils.rb +90 -0
  22. data/lib/capsulecd/base/common/validation_utils.rb +22 -0
  23. data/lib/capsulecd/base/configuration.rb +151 -0
  24. data/lib/capsulecd/base/engine.rb +163 -0
  25. data/lib/capsulecd/base/runner/circleci.rb +37 -0
  26. data/lib/capsulecd/base/runner/default.rb +38 -0
  27. data/lib/capsulecd/base/source/github.rb +183 -0
  28. data/lib/capsulecd/base/transform_engine.rb +62 -0
  29. data/lib/capsulecd/chef/chef_engine.rb +172 -0
  30. data/lib/capsulecd/chef/chef_helper.rb +29 -0
  31. data/lib/capsulecd/cli.rb +64 -0
  32. data/lib/capsulecd/error.rb +51 -0
  33. data/lib/capsulecd/javascript/javascript_engine.rb +213 -0
  34. data/lib/capsulecd/node/node_engine.rb +141 -0
  35. data/lib/capsulecd/python/python_engine.rb +157 -0
  36. data/lib/capsulecd/ruby/ruby_engine.rb +191 -0
  37. data/lib/capsulecd/ruby/ruby_helper.rb +60 -0
  38. data/lib/capsulecd/version.rb +3 -0
  39. data/lib/capsulecd.rb +16 -0
  40. data/logo.svg +1 -0
  41. data/spec/fixtures/chef/cookbook_analogj_test/CHANGELOG.md +3 -0
  42. data/spec/fixtures/chef/cookbook_analogj_test/Gemfile +18 -0
  43. data/spec/fixtures/chef/cookbook_analogj_test/LICENSE +21 -0
  44. data/spec/fixtures/chef/cookbook_analogj_test/README.md +13 -0
  45. data/spec/fixtures/chef/cookbook_analogj_test/Rakefile +1 -0
  46. data/spec/fixtures/chef/cookbook_analogj_test/Thorfile +12 -0
  47. data/spec/fixtures/chef/cookbook_analogj_test/chefignore +94 -0
  48. data/spec/fixtures/chef/cookbook_analogj_test/metadata.rb +5 -0
  49. data/spec/fixtures/chef/cookbook_analogj_test/recipes/default.rb +6 -0
  50. data/spec/fixtures/incorrect_configuration.yml +4 -0
  51. data/spec/fixtures/javascript/javascript_analogj_test/LICENSE +21 -0
  52. data/spec/fixtures/javascript/javascript_analogj_test/README.md +2 -0
  53. data/spec/fixtures/javascript/javascript_analogj_test/package.json +19 -0
  54. data/spec/fixtures/node/npm_analogj_test/LICENSE +21 -0
  55. data/spec/fixtures/node/npm_analogj_test/README.md +2 -0
  56. data/spec/fixtures/node/npm_analogj_test/package.json +19 -0
  57. data/spec/fixtures/python/pip_analogj_test/LICENSE +21 -0
  58. data/spec/fixtures/python/pip_analogj_test/MANIFEST.in +1 -0
  59. data/spec/fixtures/python/pip_analogj_test/README.md +1 -0
  60. data/spec/fixtures/python/pip_analogj_test/VERSION +1 -0
  61. data/spec/fixtures/python/pip_analogj_test/setup.cfg +5 -0
  62. data/spec/fixtures/python/pip_analogj_test/setup.py +80 -0
  63. data/spec/fixtures/python/pip_analogj_test/tox.ini +14 -0
  64. data/spec/fixtures/ruby/gem_analogj_test/Gemfile +4 -0
  65. data/spec/fixtures/ruby/gem_analogj_test/LICENSE.txt +21 -0
  66. data/spec/fixtures/ruby/gem_analogj_test/README.md +41 -0
  67. data/spec/fixtures/ruby/gem_analogj_test/Rakefile +6 -0
  68. data/spec/fixtures/ruby/gem_analogj_test/bin/console +14 -0
  69. data/spec/fixtures/ruby/gem_analogj_test/bin/setup +8 -0
  70. data/spec/fixtures/ruby/gem_analogj_test/gem_analogj_test.gemspec +25 -0
  71. data/spec/fixtures/ruby/gem_analogj_test/lib/gem_analogj_test/version.rb +3 -0
  72. data/spec/fixtures/ruby/gem_analogj_test/lib/gem_analogj_test.rb +5 -0
  73. data/spec/fixtures/ruby/gem_analogj_test/spec/gem_analogj_test_spec.rb +7 -0
  74. data/spec/fixtures/ruby/gem_analogj_test/spec/spec_helper.rb +2 -0
  75. data/spec/fixtures/ruby/gem_analogj_test-0.1.4.gem +0 -0
  76. data/spec/fixtures/sample_chef_configuration.yml +8 -0
  77. data/spec/fixtures/sample_configuration.yml +7 -0
  78. data/spec/fixtures/sample_global_configuration.yml +23 -0
  79. data/spec/fixtures/sample_node_configuration.yml +7 -0
  80. data/spec/fixtures/sample_python_configuration.yml +8 -0
  81. data/spec/fixtures/sample_repo_configuration.yml +22 -0
  82. data/spec/fixtures/sample_ruby_configuration.yml +5 -0
  83. data/spec/fixtures/vcr_cassettes/chef_build_step.yml +636 -0
  84. data/spec/fixtures/vcr_cassettes/gem_build_step.yml +653 -0
  85. data/spec/fixtures/vcr_cassettes/gem_build_step_without_version_rb.yml +653 -0
  86. data/spec/fixtures/vcr_cassettes/integration_chef.yml +1399 -0
  87. data/spec/fixtures/vcr_cassettes/integration_node.yml +1388 -0
  88. data/spec/fixtures/vcr_cassettes/integration_python.yml +1388 -0
  89. data/spec/fixtures/vcr_cassettes/integration_ruby.yml +1377 -0
  90. data/spec/fixtures/vcr_cassettes/node_build_step.yml +647 -0
  91. data/spec/fixtures/vcr_cassettes/pip_build_step.yml +653 -0
  92. data/spec/lib/capsulecd/base/configuration_spec.rb +75 -0
  93. data/spec/lib/capsulecd/base/engine_spec.rb +51 -0
  94. data/spec/lib/capsulecd/base/source/github_spec.rb +253 -0
  95. data/spec/lib/capsulecd/base/transform_engine_spec.rb +55 -0
  96. data/spec/lib/capsulecd/chef/chef_engine_spec.rb +114 -0
  97. data/spec/lib/capsulecd/cli_spec.rb +57 -0
  98. data/spec/lib/capsulecd/node/node_engine_spec.rb +113 -0
  99. data/spec/lib/capsulecd/python/python_engine_spec.rb +118 -0
  100. data/spec/lib/capsulecd/ruby/ruby_engine_spec.rb +128 -0
  101. data/spec/spec_helper.rb +105 -0
  102. data/spec/support/file_system.rb +21 -0
  103. data/spec/support/package_types.rb +11 -0
  104. metadata +281 -0
@@ -0,0 +1,213 @@
1
+ require 'semverly'
2
+ require 'open3'
3
+ require 'bundler'
4
+ require 'json'
5
+ require_relative '../base/engine'
6
+
7
+ module CapsuleCD
8
+ module Javascript
9
+ class JavascriptEngine < Engine
10
+ def build_step
11
+ super
12
+
13
+ @_is_bower = File.exist?(@source_git_local_path + '/bower.json')
14
+ @_is_npm = File.exist?(@source_git_local_path + '/package.json')
15
+
16
+ # validate that the metadata files exist
17
+ if !@_is_bower && !@_is_npm
18
+ fail CapsuleCD::Error::BuildPackageInvalid, 'package.json or bower.json file must be present for Javascript packages'
19
+ end
20
+
21
+ # we can't bump the npm version here because the npm version patch command will set it.
22
+ # howerver we need to make sure the bower.json and package.json versions are insync.
23
+ # we'll take the latest version of either the package.json or bower.json and set that as the version of both.
24
+ sync_versions
25
+
26
+ # now that the bower and package versions are in sync, lets bump the version of bower.json
27
+ # (because package.json will be bumped automatically.)
28
+ if @_is_bower
29
+ bower_file = File.read(@source_git_local_path + '/bower.json')
30
+ bower_data = JSON.parse(bower_file)
31
+ next_version = bump_version(SemVer.parse(bower_data['version']))
32
+ bower_data['version'] = next_version.to_s
33
+ File.write(@source_git_local_path + '/bower.json', JSON.pretty_generate(bower_data))
34
+ end
35
+
36
+
37
+ # TODO: check if this module name and version already exist.
38
+
39
+ # check for/create any required missing folders/files
40
+ unless File.exist?(@source_git_local_path + '/test')
41
+ FileUtils.mkdir(@source_git_local_path + '/test')
42
+ end
43
+ unless File.exist?(@source_git_local_path + '/.gitignore')
44
+ CapsuleCD::GitUtils.create_gitignore(@source_git_local_path, ['Node','Yeoman'])
45
+ end
46
+ end
47
+
48
+ def test_step
49
+ super
50
+
51
+ if @_is_npm
52
+ # the module has already been downloaded. lets make sure all its dependencies are available.
53
+ Open3.popen3('npm install', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
54
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
55
+ Thread.new do
56
+ until (line = stream_buffer.gets).nil?
57
+ puts "#{name} -> #{line}"
58
+ end
59
+ end
60
+ end
61
+ # wait for process
62
+ external.join
63
+ unless external.value.success?
64
+ fail CapsuleCD::Error::TestDependenciesError, 'npm install failed. Check module dependencies'
65
+ end
66
+ end
67
+ # create a shrinkwrap file.
68
+ Open3.popen3('npm shrinkwrap', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
69
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
70
+ Thread.new do
71
+ until (line = stream_buffer.gets).nil?
72
+ puts "#{name} -> #{line}"
73
+ end
74
+ end
75
+ end
76
+ # wait for process
77
+ external.join
78
+ unless external.value.success?
79
+ fail CapsuleCD::Error::TestDependenciesError, 'npm shrinkwrap failed. Check log for exact error'
80
+ end
81
+ end
82
+
83
+ # run test command
84
+ test_cmd = @config.engine_cmd_test || 'npm test'
85
+ Open3.popen3(ENV, test_cmd, chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
86
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
87
+ Thread.new do
88
+ until (line = stream_buffer.gets).nil?
89
+ puts "#{name} -> #{line}"
90
+ end
91
+ end
92
+ end
93
+ # wait for process
94
+ external.join
95
+ unless external.value.success?
96
+ fail CapsuleCD::Error::TestRunnerError, test_cmd + ' failed. Check log for exact error'
97
+ end
98
+ end unless @config.engine_disable_test
99
+ end
100
+
101
+ if @_is_bower
102
+ # lets make sure all the bower dependencies are available.
103
+ Open3.popen3('bower install --allow-root', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
104
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
105
+ Thread.new do
106
+ until (line = stream_buffer.gets).nil?
107
+ puts "#{name} -> #{line}"
108
+ end
109
+ end
110
+ end
111
+ # wait for process
112
+ external.join
113
+ unless external.value.success?
114
+ fail CapsuleCD::Error::TestDependenciesError, 'npm install failed. Check module dependencies'
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ # run npm publish
121
+ def package_step
122
+ super
123
+
124
+ # commit changes to the cookbook. (test run occurs before this, and it should clean up any instrumentation files, created,
125
+ # as they will be included in the commmit and any release artifacts)
126
+ CapsuleCD::GitUtils.commit(@source_git_local_path, 'Committing automated changes before packaging.') rescue puts 'No changes to commit..'
127
+ if @_is_bower && !@_is_npm
128
+ bower_file = File.read(@source_git_local_path + '/bower.json')
129
+ bower_data = JSON.parse(bower_file)
130
+ next_version = SemVer.parse(bower_data['version'])
131
+ @source_release_commit = CapsuleCD::GitUtils.tag(@source_git_local_path, "v#{next_version}")
132
+
133
+ else
134
+
135
+ # run npm publish
136
+ Open3.popen3("npm version #{@config.engine_version_bump_type} -m '(v%s) Automated packaging of release by CapsuleCD'", chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
137
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
138
+ Thread.new do
139
+ until (line = stream_buffer.gets).nil?
140
+ puts "#{name} -> #{line}"
141
+ end
142
+ end
143
+ end
144
+ # wait for process
145
+ external.join
146
+ fail 'npm version bump failed' unless external.value.success?
147
+ end
148
+ @source_release_commit = CapsuleCD::GitUtils.get_latest_tag_commit(@source_git_local_path)
149
+ end
150
+
151
+
152
+ end
153
+
154
+ # this step should push the release to the package repository (ie. npm, chef supermarket, rubygems)
155
+ def release_step
156
+ super
157
+
158
+ if @_is_npm
159
+ npmrc_path = File.expand_path('~/.npmrc')
160
+
161
+ unless @config.npm_auth_token
162
+ fail CapsuleCD::Error::ReleaseCredentialsMissing, 'cannot deploy page to npm, credentials missing'
163
+ return
164
+ end
165
+
166
+ # write the knife.rb config file.
167
+ File.open(npmrc_path, 'w+') do |file|
168
+ file.write("//registry.npmjs.org/:_authToken=#{@config.npm_auth_token}")
169
+ end
170
+
171
+ # run npm publish
172
+ Open3.popen3('npm publish .', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
173
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
174
+ Thread.new do
175
+ until (line = stream_buffer.gets).nil?
176
+ puts "#{name} -> #{line}"
177
+ end
178
+ end
179
+ end
180
+ # wait for process
181
+ external.join
182
+ unless external.value.success?
183
+ fail CapsuleCD::Error::ReleasePackageError, 'npm publish failed. Check log for exact error'
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ private
190
+ def sync_versions
191
+ #this method only needs to run if bower and package json files exist.
192
+ if !@_is_bower || !@_is_npm
193
+ return
194
+ end
195
+
196
+ bower_file = File.read(@source_git_local_path + '/bower.json')
197
+ bower_data = JSON.parse(bower_file)
198
+ bower_version = SemVer.parse(bower_data['version'])
199
+ package_file = File.read(@source_git_local_path + '/package.json')
200
+ package_data = JSON.parse(package_file)
201
+ package_version = SemVer.parse(package_data['version'])
202
+
203
+ if(bower_version>package_version)
204
+ package_data['version'] = bower_version.to_s
205
+ File.write(@source_git_local_path + '/package.json', JSON.pretty_generate(package_data))
206
+ else
207
+ bower_data['version'] = package_version.to_s
208
+ File.write(@source_git_local_path + '/bower.json', JSON.pretty_generate(bower_data))
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,141 @@
1
+ require 'semverly'
2
+ require 'open3'
3
+ require 'bundler'
4
+ require_relative '../base/engine'
5
+
6
+ module CapsuleCD
7
+ module Node
8
+ class NodeEngine < Engine
9
+ def build_step
10
+ super
11
+ # validate that the chef metadata.rb file exists
12
+ unless File.exist?(@source_git_local_path + '/package.json')
13
+ fail CapsuleCD::Error::BuildPackageInvalid, 'package.json file is required to process Npm package'
14
+ end
15
+
16
+ # no need to bump up the version here. It will automatically be bumped up via the npm version patch command.
17
+ # however we need to read the version from the package.json file and check if a npm module already exists.
18
+
19
+ # TODO: check if this module name and version already exist.
20
+
21
+ # check for/create any required missing folders/files
22
+ unless File.exist?(@source_git_local_path + '/test')
23
+ FileUtils.mkdir(@source_git_local_path + '/test')
24
+ end
25
+ unless File.exist?(@source_git_local_path + '/.gitignore')
26
+ CapsuleCD::GitUtils.create_gitignore(@source_git_local_path, ['Node'])
27
+ end
28
+ end
29
+
30
+ def test_step
31
+ super
32
+
33
+ # the module has already been downloaded. lets make sure all its dependencies are available.
34
+ Open3.popen3('npm install', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
35
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
36
+ Thread.new do
37
+ until (line = stream_buffer.gets).nil?
38
+ puts "#{name} -> #{line}"
39
+ end
40
+ end
41
+ end
42
+ # wait for process
43
+ external.join
44
+ unless external.value.success?
45
+ fail CapsuleCD::Error::TestDependenciesError, 'npm install failed. Check module dependencies'
46
+ end
47
+ end
48
+
49
+ # create a shrinkwrap file.
50
+ Open3.popen3('npm shrinkwrap', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
51
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
52
+ Thread.new do
53
+ until (line = stream_buffer.gets).nil?
54
+ puts "#{name} -> #{line}"
55
+ end
56
+ end
57
+ end
58
+ # wait for process
59
+ external.join
60
+ unless external.value.success?
61
+ fail CapsuleCD::Error::TestDependenciesError, 'npm shrinkwrap failed. Check log for exact error'
62
+ end
63
+ end
64
+
65
+ # run test command
66
+ test_cmd = @config.engine_cmd_test || 'npm test'
67
+ Open3.popen3(ENV, test_cmd, chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
68
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
69
+ Thread.new do
70
+ until (line = stream_buffer.gets).nil?
71
+ puts "#{name} -> #{line}"
72
+ end
73
+ end
74
+ end
75
+ # wait for process
76
+ external.join
77
+ unless external.value.success?
78
+ fail CapsuleCD::Error::TestRunnerError, test_cmd + ' failed. Check log for exact error'
79
+ end
80
+ end unless @config.engine_disable_test
81
+ end
82
+
83
+ # run npm publish
84
+ def package_step
85
+ super
86
+
87
+ # commit changes to the cookbook. (test run occurs before this, and it should clean up any instrumentation files, created,
88
+ # as they will be included in the commmit and any release artifacts)
89
+ CapsuleCD::GitUtils.commit(@source_git_local_path, 'Committing automated changes before packaging.') rescue puts 'No changes to commit..'
90
+
91
+ # run npm publish
92
+ Open3.popen3("npm version #{@config.engine_version_bump_type} -m '(v%s) Automated packaging of release by CapsuleCD'", chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
93
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
94
+ Thread.new do
95
+ until (line = stream_buffer.gets).nil?
96
+ puts "#{name} -> #{line}"
97
+ end
98
+ end
99
+ end
100
+ # wait for process
101
+ external.join
102
+ fail 'npm version bump failed' unless external.value.success?
103
+ end
104
+
105
+ @source_release_commit = CapsuleCD::GitUtils.get_latest_tag_commit(@source_git_local_path)
106
+ end
107
+
108
+ # this step should push the release to the package repository (ie. npm, chef supermarket, rubygems)
109
+ def release_step
110
+ super
111
+ npmrc_path = File.expand_path('~/.npmrc')
112
+
113
+ unless @config.npm_auth_token
114
+ fail CapsuleCD::Error::ReleaseCredentialsMissing, 'cannot deploy page to npm, credentials missing'
115
+ return
116
+ end
117
+
118
+ # write the knife.rb config file.
119
+ File.open(npmrc_path, 'w+') do |file|
120
+ file.write("//registry.npmjs.org/:_authToken=#{@config.npm_auth_token}")
121
+ end
122
+
123
+ # run npm publish
124
+ Open3.popen3('npm publish .', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
125
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
126
+ Thread.new do
127
+ until (line = stream_buffer.gets).nil?
128
+ puts "#{name} -> #{line}"
129
+ end
130
+ end
131
+ end
132
+ # wait for process
133
+ external.join
134
+ unless external.value.success?
135
+ fail CapsuleCD::Error::ReleasePackageError, 'npm publish failed. Check log for exact error'
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,157 @@
1
+ require 'semverly'
2
+ require 'open3'
3
+ require 'bundler'
4
+ require_relative '../base/engine'
5
+
6
+ module CapsuleCD
7
+ module Python
8
+ class PythonEngine < Engine
9
+ def build_step
10
+ super
11
+ unless File.exist?(@source_git_local_path + '/setup.py')
12
+ fail CapsuleCD::Error::BuildPackageInvalid, 'setup.py file is required to process Python package'
13
+ end
14
+
15
+ # check for/create required VERSION file
16
+ unless File.exist?(@source_git_local_path + '/VERSION')
17
+ File.open(@source_git_local_path + '/VERSION', 'w') { |file| file.write('0.0.0') }
18
+ end
19
+
20
+ # bump up the version here.
21
+ # since there's no standardized way to bump up the version in the setup.py file, we're going to assume that the version
22
+ # is specified in a VERSION file in the root of the source repository
23
+ # this is option #4 in the python packaging guide:
24
+ # https://packaging.python.org/en/latest/single_source_version/#single-sourcing-the-version
25
+ #
26
+ # additional packaging structures, like those listed below, may also be supported in the future.
27
+ # http://stackoverflow.com/a/7071358/1157633
28
+
29
+ version = File.read(@source_git_local_path + '/VERSION').strip
30
+ next_version = bump_version(SemVer.parse(version))
31
+ File.open(@source_git_local_path + '/VERSION', 'w') do |file|
32
+ file.write(next_version)
33
+ end
34
+
35
+ # make sure the package testing manager is available.
36
+ # there is a standardized way to test packages (python setup.py tests), however for automation tox is preferred
37
+ # because of virtualenv and its support for multiple interpreters.
38
+ unless File.exist?(@source_git_local_path + '/tox.ini')
39
+ # if a tox.ini file is not present, we'll create a default one and specify 'python setup.py test' as the test
40
+ # runner command, and requirements.txt as the dependencies for this package.
41
+ File.open(@source_git_local_path + '/tox.ini', 'w') { |file|
42
+ file.write(<<-TOX.gsub(/^\s+/, '')
43
+ # Tox (http://tox.testrun.org/) is a tool for running tests
44
+ # in multiple virtualenvs. This configuration file will run the
45
+ # test suite on all supported python versions. To use it, "pip install tox"
46
+ # and then run "tox" from this directory.
47
+
48
+ [tox]
49
+ envlist = py27
50
+ usedevelop = True
51
+
52
+ [testenv]
53
+ commands = python setup.py test
54
+ deps =
55
+ -rrequirements.txt
56
+ TOX
57
+ )
58
+ }
59
+ end
60
+
61
+ # check for/create any required missing folders/files
62
+ unless File.exist?(@source_git_local_path + '/requirements.txt')
63
+ File.open(@source_git_local_path + '/requirements.txt', 'w') { |file| file.write('') }
64
+ end
65
+
66
+ unless File.exist?(@source_git_local_path + '/tests')
67
+ FileUtils.mkdir(@source_git_local_path + '/tests')
68
+ end
69
+ unless File.exist?(@source_git_local_path + '/tests/__init__.py')
70
+ File.open(@source_git_local_path + '/tests/__init__.py', 'w') { |file| file.write('') }
71
+ end
72
+ unless File.exist?(@source_git_local_path + '/.gitignore')
73
+ CapsuleCD::GitUtils.create_gitignore(@source_git_local_path, ['Python'])
74
+ end
75
+ end
76
+
77
+ def test_step
78
+ super
79
+
80
+
81
+ # download the package dependencies and register it in the virtualenv using tox (which will do pip install -e .)
82
+ # https://packaging.python.org/en/latest/distributing/
83
+ # once that's done, tox will run tests
84
+ # run test command
85
+ test_cmd = @config.engine_cmd_test || 'tox'
86
+ Open3.popen3(test_cmd, chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
87
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
88
+ Thread.new do
89
+ until (line = stream_buffer.gets).nil?
90
+ puts "#{name} -> #{line}"
91
+ end
92
+ end
93
+ end
94
+ # wait for process
95
+ external.join
96
+ unless external.value.success?
97
+ fail CapsuleCD::Error::TestDependenciesError, test_cmd + ' failed to test package.'
98
+ end
99
+ end unless @config.engine_disable_test
100
+ end
101
+
102
+ # run npm publish
103
+ def package_step
104
+ super
105
+
106
+ # commit changes to the cookbook. (test run occurs before this, and it should clean up any instrumentation files, created,
107
+ # as they will be included in the commmit and any release artifacts)
108
+ version = File.read(@source_git_local_path + '/VERSION').strip
109
+ next_version = SemVer.parse(version)
110
+ CapsuleCD::GitUtils.commit(@source_git_local_path, "(v#{next_version}) Automated packaging of release by CapsuleCD")
111
+ @source_release_commit = CapsuleCD::GitUtils.tag(@source_git_local_path, "v#{next_version}")
112
+ end
113
+
114
+ # this step should push the release to the package repository (ie. npm, chef supermarket, rubygems)
115
+ def release_step
116
+ super
117
+ pypirc_path = File.expand_path('~/.pypirc')
118
+
119
+ unless @config.pypi_username || @config.pypi_password
120
+ fail CapsuleCD::Error::ReleaseCredentialsMissing, 'cannot deploy package to pip, credentials missing'
121
+ return
122
+ end
123
+
124
+ # write the knife.rb config file.
125
+ File.open(pypirc_path, 'w+') do |file|
126
+ file.write(<<-EOT.gsub(/^\s+/, '')
127
+ [distutils]
128
+ index-servers=pypi
129
+
130
+ [pypi]
131
+ repository = https://pypi.python.org/pypi
132
+ username = #{@config.pypi_username}
133
+ password = #{@config.pypi_password}
134
+ EOT
135
+ )
136
+ end
137
+
138
+ # run python setup.py sdist upload
139
+ # TODO: use twine instead (it supports HTTPS.)https://python-packaging-user-guide.readthedocs.org/en/latest/distributing/#uploading-your-project-to-pypi
140
+ Open3.popen3('python setup.py sdist upload', chdir: @source_git_local_path) do |_stdin, stdout, stderr, external|
141
+ { stdout: stdout, stderr: stderr }. each do |name, stream_buffer|
142
+ Thread.new do
143
+ until (line = stream_buffer.gets).nil?
144
+ puts "#{name} -> #{line}"
145
+ end
146
+ end
147
+ end
148
+ # wait for process
149
+ external.join
150
+ unless external.value.success?
151
+ fail CapsuleCD::Error::ReleasePackageError, 'python setup.py upload failed. Check log for exact error'
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end