releasinator 0.1.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.
data/lib/validator.rb ADDED
@@ -0,0 +1,312 @@
1
+ require 'colorize'
2
+ require 'fileutils'
3
+ require_relative 'command_processor'
4
+ require_relative 'downstream_repo'
5
+ require_relative 'github_repo'
6
+ require_relative 'printer'
7
+ require_relative 'validator_changelog'
8
+
9
+ module Releasinator
10
+ class Validator
11
+
12
+ def initialize(releasinator_config)
13
+ @releasinator_config = releasinator_config
14
+ end
15
+
16
+ def get_changelog_contents(base_dir)
17
+ Dir.chdir(base_dir) do
18
+ open('CHANGELOG.md').read
19
+ end
20
+ end
21
+
22
+ def validate_git_version
23
+ version_output = CommandProcessor.command("git version")
24
+ # version where the parallel git fetch features were added
25
+ expected_git_version = "2.8.0"
26
+ actual_git_version = version_output.split[2]
27
+
28
+ if Gem::Version.new(expected_git_version) > Gem::Version.new(actual_git_version)
29
+ Printer.fail("Actual git version " + actual_git_version.bold + " is smaller than expected git version " + expected_git_version.bold)
30
+ abort()
31
+ else
32
+ Printer.success("Git version " + actual_git_version.bold + " found, and is higher than or equal to expected git version " + expected_git_version.bold)
33
+ end
34
+ end
35
+
36
+ def validate_changelog(base_dir, downstream_dir)
37
+ validate_exist(base_dir, "CHANGELOG.md", downstream_dir, ["release_notes.md"])
38
+
39
+ changelog_contents = get_changelog_contents(base_dir)
40
+ ValidatorChangelog.new.validate_changelog_contents(changelog_contents)
41
+ end
42
+
43
+ def validate_is_type(obj, type)
44
+ if !obj.is_a? type
45
+ Printer.fail("#{obj} is not a #{type}.")
46
+ abort()
47
+ end
48
+ end
49
+
50
+ def validate_method_convention(hash)
51
+ hash.each do |key, value|
52
+ if key.to_s.end_with? "_methods"
53
+ # validate that anything ending in _methods is a list of methods
54
+ if !value.respond_to? :each
55
+ Printer.fail("#{key} is not a list.")
56
+ abort()
57
+ end
58
+ value.each do |list_item|
59
+ validate_is_type list_item, Method
60
+ end
61
+ elsif key.to_s.end_with? "_method"
62
+ # anything ending in _method is a method
63
+ validate_is_type value, Method
64
+ else
65
+ # ignore everything else
66
+ end
67
+ end
68
+ end
69
+
70
+ def validate_required_configatron_key(key)
71
+ if !@releasinator_config.has_key?(key)
72
+ Printer.fail("No #{key} found in configatron.")
73
+ abort()
74
+ end
75
+ end
76
+
77
+ def validate_config()
78
+ validate_required_configatron_key(:product_name)
79
+ validate_required_configatron_key(:prerelease_checklist_items)
80
+ validate_required_configatron_key(:build_method)
81
+ validate_required_configatron_key(:publish_to_package_manager_method)
82
+ validate_required_configatron_key(:wait_for_package_manager_method)
83
+ validate_required_configatron_key(:release_to_github)
84
+
85
+ validate_method_convention(@releasinator_config)
86
+
87
+ if @releasinator_config.has_key? :downstream_repos
88
+ @releasinator_config[:downstream_repos].each do |downsteam_repo|
89
+ validate_is_type downsteam_repo, DownstreamRepo
90
+
91
+ validate_method_convention(downsteam_repo.options)
92
+ end
93
+ end
94
+ end
95
+
96
+ def validate_github_permissions(repo_url)
97
+ github_repo = GitHubRepo.new(repo_url)
98
+ github_client = github_repo.client
99
+
100
+ begin
101
+ # get the list of collaborators.
102
+ puts "Checking collaborators on #{repo_url}." if @releasinator_config[:verbose]
103
+ github_collaborators = github_client.collaborators "#{github_repo.org}/#{github_repo.repo}"
104
+ if ! github_collaborators
105
+ Printer.fail("request failed with code:#{res.code}\nbody:#{res.body}")
106
+ abort()
107
+ end
108
+ puts github_collaborators.inspect if @releasinator_config[:trace]
109
+ Printer.success("User has push permissions on #{repo_url}.")
110
+ rescue => error
111
+ #This will fail if the user does not have push permissions.
112
+ Printer.fail(error.inspect)
113
+ abort()
114
+ end
115
+ end
116
+
117
+ def validate_gitignore(line, is_downstream_present)
118
+ if !File.exist?(".gitignore")
119
+ FileUtils.touch('.gitignore')
120
+ CommandProcessor.command("git add . && git commit -m \"#{@releasinator_config[:releasinator_name]}: add .gitignore\"")
121
+ end
122
+
123
+ if is_downstream_present
124
+ if !line_match_in_file?(line, ".gitignore")
125
+ is_git_already_clean = GitUtil.new().is_clean_git?
126
+ File.open('.gitignore', 'a') do |f|
127
+ f.puts "# #{@releasinator_config[:releasinator_name]}"
128
+ f.puts line
129
+ end
130
+
131
+ if is_git_already_clean
132
+ CommandProcessor.command("git add . && git commit -m \"#{@releasinator_config[:releasinator_name]}: add downstream dir to .gitignore\"")
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def line_match_in_file?(contains_string, filename)
139
+ File.open("#{filename}", "r") do |f|
140
+ f.each_line do |line|
141
+ if line.match /^#{Regexp.escape(contains_string)}$/
142
+ Printer.success("#{filename} contains #{contains_string}")
143
+ return true
144
+ end
145
+ end
146
+ end
147
+ false
148
+ end
149
+
150
+ def validate_referenced_in_readme(base_dir, filename)
151
+ Dir.chdir(base_dir) do
152
+ File.open("README.md", "r") do |f|
153
+ f.each_line do |line|
154
+ if line.include? "(#{filename})"
155
+ Printer.success("#{filename} referenced in #{base_dir}/README.md")
156
+ return
157
+ end
158
+ end
159
+ end
160
+ end
161
+ Printer.fail("Please link to the #{filename} file somewhere in #{base_dir}/README.md.")
162
+ abort()
163
+ end
164
+
165
+ def validate_exist(base_dir, expected_file_name, downstream_dir, alternate_names=[])
166
+ Dir.chdir(base_dir) do
167
+ if !File.exist?(expected_file_name)
168
+ puts "#{base_dir}/#{expected_file_name} not found. Searching for similar files.".yellow
169
+
170
+ # search for files that are somewhat similar to the file being searched, ignoring case
171
+ filename_prefix = expected_file_name[0,5]
172
+ similar_files = CommandProcessor.command("find . -type f -not -path \"./#{downstream_dir}/*\" -iname '#{filename_prefix}*'| sed 's|./||'").strip
173
+ num_similar_files = similar_files.split.count
174
+ puts similar_files
175
+ if num_similar_files == 1
176
+ Printer.check_proceed("Found a single similar file: #{similar_files}. Do you want to rename this to the expected #{expected_file_name}?","Please place #{base_dir}/#{expected_file_name}")
177
+ rename_file(similar_files, expected_file_name)
178
+ elsif num_similar_files > 1
179
+ Printer.fail("Found more than 1 file similar to #{expected_file_name}. Please rename one, and optionally remove the others to not confuse users.")
180
+ abort()
181
+ elsif !rename_alternate_name(expected_file_name, alternate_names)
182
+ Printer.fail("Please place #{base_dir}/#{expected_file_name}.")
183
+ abort()
184
+ end
185
+ end
186
+ Printer.success("#{base_dir}/#{expected_file_name} found.")
187
+ end
188
+ end
189
+
190
+ def validate_clean_git
191
+ untracked_files = GitUtil.untracked_files
192
+ diff = GitUtil.diff
193
+ diff_cached = GitUtil.cached
194
+
195
+ if '' != untracked_files
196
+ puts untracked_files.red if @releasinator_config[:verbose]
197
+ error = true
198
+ Printer.fail("Untracked files found.")
199
+ else
200
+ Printer.success("No untracked files found.")
201
+ end
202
+
203
+ if '' != diff
204
+ puts diff.red if @releasinator_config[:verbose]
205
+ error = true
206
+ Printer.fail("Unstaged changes found.")
207
+ else
208
+ Printer.success("No unstaged changes found.")
209
+ end
210
+
211
+ if '' != diff_cached
212
+ puts diff_cached.red if @releasinator_config[:verbose]
213
+ error = true
214
+ Printer.fail("Uncommitted changes found.")
215
+ else
216
+ Printer.success("No uncommitted changes found.")
217
+ end
218
+
219
+ abort() if error
220
+ end
221
+
222
+ class Submodule
223
+ attr_reader :name, :path, :url
224
+
225
+ def initialize(name, path, url)
226
+ @name=name
227
+ @path=path
228
+ @url=url
229
+ end
230
+ end
231
+
232
+ def validate_submodules
233
+ if File.exist?(".gitmodules")
234
+ submodules = Array.new
235
+
236
+ current_name = nil
237
+ current_path = nil
238
+ current_url = nil
239
+ File.open(".gitmodules", "r") do |f|
240
+ f.each_line do |line|
241
+
242
+ if line.include? "\""
243
+ current_name = line.strip.split(' ').last.to_s.split("\"").at(1)
244
+ elsif line.include? "path = "
245
+ current_path = line.strip.split(' ').last.to_s
246
+ elsif line.include? "url = "
247
+ current_url = line.strip.split(' ').last.to_s
248
+ submodules << Submodule.new(current_name, current_path, current_url)
249
+ end
250
+ end
251
+ end
252
+
253
+ Printer.success("Found " + submodules.count.to_s.bold + " submodules in .gitmodules.")
254
+ submodules.each do |submodule|
255
+ Dir.chdir(submodule.path) do
256
+ validate_matches_branch("master", "Submodule")
257
+ end
258
+ end
259
+ else
260
+ Printer.success("No submodules found.")
261
+ end
262
+ end
263
+
264
+ def validate_matches_branch(branch_name, console_prefix="Root")
265
+ current_dir = Dir.pwd
266
+
267
+ # Don't fetch the submodules, as they should already be fetched by the initial recursive fetch.
268
+ if console_prefix == "Root"
269
+ puts "fetching #{current_dir}" if @releasinator_config[:verbose]
270
+
271
+ # silently fails if it can't connect because sometimes we want to release even if
272
+ # corp GitHub is down.
273
+ `git fetch --recurse-submodules -j9`
274
+ end
275
+
276
+ validate_clean_git()
277
+
278
+ head_sha1 = `git rev-parse --verify head`.strip
279
+ origin_branch_sha1 = `git rev-parse --verify origin/#{branch_name}`.strip
280
+ if head_sha1 != origin_branch_sha1
281
+ abort_string = "#{console_prefix} #{current_dir} at #{head_sha1}, but origin/#{branch_name} is #{origin_branch_sha1}."\
282
+ "\nIf you received this error on the root project, you may need to:"\
283
+ "\n 1. pull the latest changes from the remote,"\
284
+ "\n 2. push changes up to the remote,"\
285
+ "\n 3. back out a current release in progress."
286
+ Printer.fail(abort_string)
287
+ abort()
288
+ else
289
+ Printer.success("#{console_prefix} #{current_dir} matches origin/#{branch_name}.")
290
+ end
291
+ end
292
+
293
+ def rename_file(old_name, new_name)
294
+ puts "Renaming #{old_name} to expected filename: #{new_name}".yellow
295
+ CommandProcessor.command("mv #{old_name} #{new_name}")
296
+ # fix any references to file in readme
297
+ replace_string("README.md", "(#{old_name})", "(#{new_name})")
298
+ CommandProcessor.command("git add . && git commit -m \"#{@config[:releasinator_name]}: rename #{old_name} to #{new_name}\"")
299
+ end
300
+
301
+ def rename_alternate_name(expected_file_name, alternate_names)
302
+ alternate_names.each do |name|
303
+ if '' != CommandProcessor.command("ls #{name}")
304
+ puts "Found similar file: #{name}."
305
+ rename_file(name, expected_file_name)
306
+ return true
307
+ end
308
+ end
309
+ false
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,82 @@
1
+ require 'colorize'
2
+ require 'Vandamme'
3
+ require 'semantic'
4
+ require_relative "current_release"
5
+ require_relative 'printer'
6
+
7
+ module Releasinator
8
+ class ValidatorChangelog
9
+
10
+ def validate_semver(changelog_hash, prefix="")
11
+ newer_version = nil
12
+ changelog_hash.each do |key,value|
13
+ if !key.start_with? prefix
14
+ Printer.fail("version #{key} does not start with prefix '#{prefix}'.")
15
+ abort()
16
+ end
17
+ older_version = Semantic::Version.new key[prefix.length..-1]
18
+
19
+ if nil != newer_version
20
+ version_comp = newer_version <=> older_version
21
+ if version_comp < 1
22
+ Printer.fail("Semver releases out of order: #{older_version} should be smaller than #{newer_version}")
23
+ abort()
24
+ end
25
+
26
+ error_suffix = "version increment error - comparing #{newer_version} to #{older_version} does not pass semver validation."
27
+ # validate the next sequence in semver
28
+ if newer_version.major == older_version.major
29
+ if newer_version.minor == older_version.minor
30
+ check_semver_criteria(newer_version.patch == older_version.patch + 1, "patch #{error_suffix}")
31
+ else
32
+ check_semver_criteria(newer_version.minor == older_version.minor + 1 && newer_version.patch == 0, "minor #{error_suffix}")
33
+ end
34
+ else
35
+ check_semver_criteria(newer_version.major == older_version.major + 1 && newer_version.minor == 0 && newer_version.patch == 0, "major #{error_suffix}")
36
+ end
37
+ end
38
+ newer_version = older_version
39
+ end
40
+ end
41
+
42
+ def check_semver_criteria(condition, message)
43
+ if !condition
44
+ Printer.fail(message)
45
+ abort()
46
+ end
47
+ end
48
+
49
+ def validate_changelog_contents(changelog_contents, prefix="")
50
+ version_header_regexes = [
51
+ ## h2 using --- separator. Example:
52
+ # 1.0.0
53
+ # -----
54
+ # First release!
55
+ '(^\d+\.\d+\.\d+).*\n----.*',
56
+
57
+ # h1/h2 header retrieved from https://github.com/tech-angels/vandamme/#format
58
+ '^#{0,3} ?([\w\d\.-]+\.[\w\d\.-]+[a-zA-Z0-9])(?: \/ (\w+ \d{1,2}(?:st|nd|rd|th)?,\s\d{4}|\d{4}-\d{2}-\d{2}|\w+))?\n?[=-]*'
59
+ ]
60
+
61
+ changelog_hash = nil
62
+ version_header_regexes.each do |version_header_regex|
63
+ parser = Vandamme::Parser.new(changelog: changelog_contents, version_header_exp: version_header_regex, format: 'markdown')
64
+ changelog_hash = parser.parse
65
+
66
+ break if !changelog_hash.empty?
67
+ end
68
+
69
+ if changelog_hash.empty?
70
+ Printer.fail("Unable to find any releases in the CHANGELOG.md. Please check that your formatting is correct.")
71
+ abort()
72
+ end
73
+
74
+ Printer.success("Found " + changelog_hash.count.to_s.bold + " release(s) in CHANGELOG.md.")
75
+
76
+ validate_semver(changelog_hash, prefix)
77
+
78
+ latest_release, latest_release_changelog = changelog_hash.first
79
+ CurrentRelease.new(latest_release, latest_release_changelog)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'releasinator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "releasinator"
8
+ spec.version = Releasinator::VERSION
9
+ spec.authors = ["PayPal"]
10
+ spec.email = ["DL-PP-RUBY-SDK@paypal.com"]
11
+ spec.summary = %q{The releasinator assists in building and releasing SDKs across languages.}
12
+ spec.description = %q{The releasinator assists in building and releasing SDKs across languages.}
13
+ spec.homepage = "https://developer.paypal.com"
14
+ spec.license = "Apache-2.0"
15
+
16
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
+ # delete this section to allow pushing this gem to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.11"
30
+ spec.add_development_dependency "rake", "~> 11.1"
31
+
32
+ spec.add_dependency "configatron", "~> 4.5"
33
+ spec.add_dependency "colorize", "~> 0.7"
34
+ spec.add_dependency "vandamme", "~> 0.0.11"
35
+ spec.add_dependency "semantic", "~> 1.4"
36
+ spec.add_dependency "json", "~> 1.8"
37
+ spec.add_dependency "octokit", "~> 4.0"
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: releasinator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - PayPal
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '11.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '11.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: configatron
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: colorize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.7'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vandamme
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.11
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.11
83
+ - !ruby/object:Gem::Dependency
84
+ name: semantic
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: json
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.8'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.8'
111
+ - !ruby/object:Gem::Dependency
112
+ name: octokit
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ description: The releasinator assists in building and releasing SDKs across languages.
126
+ email:
127
+ - DL-PP-RUBY-SDK@paypal.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - Gemfile
134
+ - Gemfile.lock
135
+ - README.md
136
+ - Rakefile
137
+ - lib/command_processor.rb
138
+ - lib/config_hash.rb
139
+ - lib/copy_file.rb
140
+ - lib/current_release.rb
141
+ - lib/default_config.rb
142
+ - lib/downstream_repo.rb
143
+ - lib/git_util.rb
144
+ - lib/github_repo.rb
145
+ - lib/printer.rb
146
+ - lib/publisher.rb
147
+ - lib/releasinator/version.rb
148
+ - lib/tasks/releasinator.rake
149
+ - lib/validator.rb
150
+ - lib/validator_changelog.rb
151
+ - releasinator.gemspec
152
+ homepage: https://developer.paypal.com
153
+ licenses:
154
+ - Apache-2.0
155
+ metadata:
156
+ allowed_push_host: https://rubygems.org
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.5.1
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: The releasinator assists in building and releasing SDKs across languages.
177
+ test_files: []