modulesync 0.10.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -43,7 +43,8 @@ module ModuleSync
43
43
  :default => CLI.defaults[:git_base] || 'git@github.com:'
44
44
  class_option :namespace,
45
45
  :aliases => '-n',
46
- :desc => 'Remote github namespace (user or organization) to clone from and push to. Defaults to puppetlabs',
46
+ :desc => 'Remote github namespace (user or organization) to clone from and push to.' \
47
+ ' Defaults to puppetlabs',
47
48
  :default => CLI.defaults[:namespace] || 'puppetlabs'
48
49
  class_option :filter,
49
50
  :aliases => '-f',
@@ -53,7 +54,8 @@ module ModuleSync
53
54
  :desc => 'A regular expression to skip repositories.'
54
55
  class_option :branch,
55
56
  :aliases => '-b',
56
- :desc => 'Branch name to make the changes in. Defaults to the default branch of the upstream repository, but falls back to "master".',
57
+ :desc => 'Branch name to make the changes in.' \
58
+ ' Defaults to the default branch of the upstream repository, but falls back to "master".',
57
59
  :default => CLI.defaults[:branch]
58
60
 
59
61
  desc 'update', 'Update the modules in managed_modules.yml'
@@ -63,7 +65,10 @@ module ModuleSync
63
65
  :default => CLI.defaults[:message]
64
66
  option :configs,
65
67
  :aliases => '-c',
66
- :desc => 'The local directory or remote repository to define the list of managed modules, the file templates, and the default values for template variables.'
68
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
69
+ ' the file templates, and the default values for template variables.'
70
+ option :managed_modules_conf,
71
+ :desc => 'The file name to define the list of managed modules'
67
72
  option :remote_branch,
68
73
  :aliases => '-r',
69
74
  :desc => 'Remote branch name to push the changes to. Defaults to the branch name.',
@@ -87,14 +92,18 @@ module ModuleSync
87
92
  :default => false
88
93
  option :pr,
89
94
  :type => :boolean,
90
- :desc => 'Submit GitHub PR',
95
+ :desc => 'Submit pull/merge request',
91
96
  :default => false
92
97
  option :pr_title,
93
- :desc => 'Title of GitHub PR',
98
+ :desc => 'Title of pull/merge request',
94
99
  :default => CLI.defaults[:pr_title] || 'Update to module template files'
95
100
  option :pr_labels,
96
- :desc => 'Labels to add to the GitHub PR',
101
+ :type => :array,
102
+ :desc => 'Labels to add to the pull/merge request',
97
103
  :default => CLI.defaults[:pr_labels] || []
104
+ option :pr_target_branch,
105
+ :desc => 'Target branch for the pull/merge request',
106
+ :default => CLI.defaults[:pr_target_branch] || 'master'
98
107
  option :offline,
99
108
  :type => :boolean,
100
109
  :desc => 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
@@ -114,18 +123,21 @@ module ModuleSync
114
123
  option :tag_pattern,
115
124
  :desc => 'The pattern to use when tagging releases.'
116
125
  option :pre_commit_script,
117
- :desc => 'A script to be run before commiting',
126
+ :desc => 'A script to be run before committing',
118
127
  :default => CLI.defaults[:pre_commit_script]
119
128
  option :fail_on_warnings,
120
129
  :type => :boolean,
121
130
  :aliases => '-F',
122
- :desc => 'Produce a failure exit code when there are warnings (only has effect when --skip_broken is enabled)',
131
+ :desc => 'Produce a failure exit code when there are warnings' \
132
+ ' (only has effect when --skip_broken is enabled)',
123
133
  :default => false
124
134
 
125
135
  def update
126
136
  config = { :command => 'update' }.merge(options)
127
137
  config = Util.symbolize_keys(config)
128
- raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop] || config[:message] || config[:offline]
138
+ raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop] \
139
+ || config[:message] \
140
+ || config[:offline]
129
141
  config[:git_opts] = { 'amend' => config[:amend], 'force' => config[:force] }
130
142
  ModuleSync.update(config)
131
143
  end
@@ -46,12 +46,13 @@ module ModuleSync
46
46
  end
47
47
 
48
48
  def self.pull(git_base, name, branch, project_root, opts)
49
+ puts "Syncing #{name}"
49
50
  Dir.mkdir(project_root) unless Dir.exist?(project_root)
50
51
 
51
52
  # Repo needs to be cloned in the cwd
52
53
  if !Dir.exist?("#{project_root}/#{name}") || !Dir.exist?("#{project_root}/#{name}/.git")
53
54
  puts 'Cloning repository fresh'
54
- remote = opts[:remote] || (git_base.start_with?('file://') ? "#{git_base}/#{name}" : "#{git_base}/#{name}.git")
55
+ remote = opts[:remote] || (git_base.start_with?('file://') ? "#{git_base}#{name}" : "#{git_base}#{name}.git")
55
56
  local = "#{project_root}/#{name}"
56
57
  puts "Cloning from #{remote}"
57
58
  repo = ::Git.clone(remote, local)
@@ -0,0 +1,56 @@
1
+ require 'octokit'
2
+ require 'modulesync/util'
3
+
4
+ module ModuleSync
5
+ module PR
6
+ # GitHub creates and manages pull requests on github.com or GitHub
7
+ # Enterprise installations.
8
+ class GitHub
9
+ def initialize(token, endpoint)
10
+ Octokit.configure do |c|
11
+ c.api_endpoint = endpoint
12
+ end
13
+ @api = Octokit::Client.new(:access_token => token)
14
+ end
15
+
16
+ def manage(namespace, module_name, options)
17
+ repo_path = File.join(namespace, module_name)
18
+ head = "#{namespace}:#{options[:branch]}"
19
+ target_branch = options[:pr_target_branch] || 'master'
20
+
21
+ if options[:noop]
22
+ $stdout.puts \
23
+ "Using no-op. Would submit PR '#{options[:pr_title]}' to #{repo_path} " \
24
+ "- merges #{options[:branch]} into #{target_branch}"
25
+ return
26
+ end
27
+
28
+ pull_requests = @api.pull_requests(repo_path,
29
+ :state => 'open',
30
+ :base => target_branch,
31
+ :head => head)
32
+ unless pull_requests.empty?
33
+ # Skip creating the PR if it exists already.
34
+ $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{options[:branch]}"
35
+ return
36
+ end
37
+
38
+ pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
39
+ pr = @api.create_pull_request(repo_path,
40
+ target_branch,
41
+ options[:branch],
42
+ options[:pr_title],
43
+ options[:message])
44
+ $stdout.puts \
45
+ "Submitted PR '#{options[:pr_title]}' to #{repo_path} " \
46
+ "- merges #{options[:branch]} into #{target_branch}"
47
+
48
+ # We only assign labels to the PR if we've discovered a list > 1. The labels MUST
49
+ # already exist. We DO NOT create missing labels.
50
+ return if pr_labels.empty?
51
+ $stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
52
+ @api.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
1
+ require 'gitlab'
2
+ require 'modulesync/util'
3
+
4
+ module ModuleSync
5
+ module PR
6
+ # GitLab creates and manages merge requests on gitlab.com or private GitLab
7
+ # installations.
8
+ class GitLab
9
+ def initialize(token, endpoint)
10
+ @api = Gitlab::Client.new(
11
+ :endpoint => endpoint,
12
+ :private_token => token
13
+ )
14
+ end
15
+
16
+ def manage(namespace, module_name, options)
17
+ repo_path = File.join(namespace, module_name)
18
+ head = "#{namespace}:#{options[:branch]}"
19
+ target_branch = options[:pr_target_branch] || 'master'
20
+
21
+ if options[:noop]
22
+ $stdout.puts \
23
+ "Using no-op. Would submit MR '#{options[:pr_title]}' to #{repo_path} " \
24
+ "- merges #{options[:branch]} into #{target_branch}"
25
+ return
26
+ end
27
+
28
+ merge_requests = @api.merge_requests(repo_path,
29
+ :state => 'opened',
30
+ :source_branch => head,
31
+ :target_branch => target_branch)
32
+ unless merge_requests.empty?
33
+ # Skip creating the MR if it exists already.
34
+ $stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{options[:branch]}"
35
+ return
36
+ end
37
+
38
+ mr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
39
+ mr = @api.create_merge_request(repo_path,
40
+ options[:pr_title],
41
+ :source_branch => options[:branch],
42
+ :target_branch => target_branch,
43
+ :labels => mr_labels)
44
+ $stdout.puts \
45
+ "Submitted MR '#{options[:pr_title]}' to #{repo_path} " \
46
+ "- merges #{options[:branch]} into #{target_branch}"
47
+
48
+ return if mr_labels.empty?
49
+ $stdout.puts "Attached the following labels to MR #{mr.iid}: #{mr_labels.join(', ')}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -4,8 +4,9 @@ require 'find'
4
4
  module ModuleSync
5
5
  module Renderer
6
6
  class ForgeModuleFile
7
- def initialize(configs = {})
7
+ def initialize(configs = {}, metadata = {})
8
8
  @configs = configs
9
+ @metadata = metadata
9
10
  end
10
11
  end
11
12
 
@@ -26,8 +27,8 @@ module ModuleSync
26
27
  File.delete(file) if File.exist?(file)
27
28
  end
28
29
 
29
- def self.render(_template, configs = {})
30
- ForgeModuleFile.new(configs).render
30
+ def self.render(_template, configs = {}, metadata = {})
31
+ ForgeModuleFile.new(configs, metadata).render
31
32
  end
32
33
 
33
34
  def self.sync(template, target_name)
@@ -19,10 +19,9 @@ module ModuleSync
19
19
 
20
20
  def build_file_configs(target_name)
21
21
  file_def = lookup_config(defaults, target_name)
22
- file_md = lookup_config(module_defaults, target_name)
23
22
  file_mc = lookup_config(module_configs, target_name)
24
23
 
25
- global_defaults.merge(file_def).merge(file_md).merge(file_mc).merge(additional_settings)
24
+ global_defaults.merge(file_def).merge(module_defaults).merge(file_mc).merge(additional_settings)
26
25
  end
27
26
 
28
27
  def managed?(target_name)
@@ -3,7 +3,10 @@ require 'yaml'
3
3
  module ModuleSync
4
4
  module Util
5
5
  def self.symbolize_keys(hash)
6
- hash.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo }
6
+ hash.inject({}) do |memo, (k, v)|
7
+ memo[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
8
+ memo
9
+ end
7
10
  end
8
11
 
9
12
  def self.parse_config(config_file)
@@ -3,27 +3,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = 'modulesync'
6
- spec.version = '0.10.0'
6
+ spec.version = '2.0.0'
7
7
  spec.authors = ['Vox Pupuli']
8
8
  spec.email = ['voxpupuli@groups.io']
9
9
  spec.summary = 'Puppet Module Synchronizer'
10
10
  spec.description = 'Utility to synchronize common files across puppet modules in Github.'
11
11
  spec.homepage = 'http://github.com/voxpupuli/modulesync'
12
12
  spec.license = 'Apache-2.0'
13
- spec.required_ruby_version = '>= 2.0.0'
13
+ spec.required_ruby_version = '>= 2.5.0'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0")
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency 'aruba'
20
+ spec.add_development_dependency 'aruba', '~> 0.14'
21
21
  spec.add_development_dependency 'bundler'
22
22
  spec.add_development_dependency 'rake'
23
23
  spec.add_development_dependency 'rspec'
24
24
  spec.add_development_dependency 'rubocop', '~> 0.50.0'
25
25
 
26
26
  spec.add_runtime_dependency 'git', '~>1.3'
27
+ spec.add_runtime_dependency 'gitlab', '~>4.0'
27
28
  spec.add_runtime_dependency 'octokit', '~>4.0'
28
29
  spec.add_runtime_dependency 'puppet-blacksmith', '~>3.0'
29
30
  spec.add_runtime_dependency 'thor'
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'modulesync/pr/github'
3
+
4
+ describe ModuleSync::PR::GitHub do
5
+ context '::manage' do
6
+ before(:each) do
7
+ @git_repo = 'test/modulesync'
8
+ @namespace, @repo_name = @git_repo.split('/')
9
+ @options = {
10
+ :pr => true,
11
+ :pr_title => 'Test PR is submitted',
12
+ :branch => 'test',
13
+ :message => 'Hello world',
14
+ :pr_auto_merge => false,
15
+ }
16
+
17
+ @client = double()
18
+ allow(Octokit::Client).to receive(:new).and_return(@client)
19
+ @it = ModuleSync::PR::GitHub.new('test', 'https://api.github.com')
20
+ end
21
+
22
+ it 'submits PR when --pr is set' do
23
+ allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])
24
+ expect(@client).to receive(:create_pull_request).with(@git_repo, 'master', @options[:branch], @options[:pr_title], @options[:message]).and_return({"html_url" => "http://example.com/pulls/22"})
25
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Submitted PR/).to_stdout
26
+ end
27
+
28
+ it 'skips submitting PR if one has already been issued' do
29
+ pr = {
30
+ "title" => "Test title",
31
+ "html_url" => "https://example.com/pulls/44",
32
+ "number" => "44"
33
+ }
34
+
35
+ expect(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([pr])
36
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Skipped! 1 PRs found for branch test/).to_stdout
37
+ end
38
+
39
+ it 'adds labels to PR when --pr-labels is set' do
40
+ @options[:pr_labels] = "HELLO,WORLD"
41
+
42
+ allow(@client).to receive(:create_pull_request).and_return({"html_url" => "http://example.com/pulls/22", "number" => "44"})
43
+ allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])
44
+
45
+ expect(@client).to receive(:add_labels_to_an_issue).with(@git_repo, "44", ["HELLO", "WORLD"])
46
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Attaching the following labels to PR 44: HELLO, WORLD/).to_stdout
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'modulesync/pr/gitlab'
3
+
4
+ describe ModuleSync::PR::GitLab do
5
+ context '::manage' do
6
+ before(:each) do
7
+ @git_repo = 'test/modulesync'
8
+ @namespace, @repo_name = @git_repo.split('/')
9
+ @options = {
10
+ :pr => true,
11
+ :pr_title => 'Test PR is submitted',
12
+ :branch => 'test',
13
+ :message => 'Hello world',
14
+ :pr_auto_merge => false,
15
+ }
16
+
17
+ @client = double()
18
+ allow(Gitlab::Client).to receive(:new).and_return(@client)
19
+ @it = ModuleSync::PR::GitLab.new('test', 'https://gitlab.com/api/v4')
20
+ end
21
+
22
+ it 'submits MR when --pr is set' do
23
+ allow(@client).to receive(:merge_requests)
24
+ .with(@git_repo,
25
+ :state => 'opened',
26
+ :source_branch => "#{@namespace}:#{@options[:branch]}",
27
+ :target_branch => 'master',
28
+ ).and_return([])
29
+
30
+ expect(@client).to receive(:create_merge_request)
31
+ .with(@git_repo,
32
+ @options[:pr_title],
33
+ :labels => [],
34
+ :source_branch => @options[:branch],
35
+ :target_branch => 'master',
36
+ ).and_return({"html_url" => "http://example.com/pulls/22"})
37
+
38
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Submitted MR/).to_stdout
39
+ end
40
+
41
+ it 'skips submitting MR if one has already been issued' do
42
+ mr = {
43
+ "title" => "Test title",
44
+ "html_url" => "https://example.com/pulls/44",
45
+ "iid" => "44"
46
+ }
47
+
48
+ expect(@client).to receive(:merge_requests)
49
+ .with(@git_repo,
50
+ :state => 'opened',
51
+ :source_branch => "#{@namespace}:#{@options[:branch]}",
52
+ :target_branch => 'master',
53
+ ).and_return([mr])
54
+
55
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Skipped! 1 MRs found for branch test/).to_stdout
56
+ end
57
+
58
+ it 'adds labels to MR when --pr-labels is set' do
59
+ @options[:pr_labels] = "HELLO,WORLD"
60
+ mr = double()
61
+ allow(mr).to receive(:iid).and_return("42")
62
+
63
+ expect(@client).to receive(:create_merge_request)
64
+ .with(@git_repo,
65
+ @options[:pr_title],
66
+ :labels => ["HELLO", "WORLD"],
67
+ :source_branch => @options[:branch],
68
+ :target_branch => 'master',
69
+ ).and_return(mr)
70
+
71
+ allow(@client).to receive(:merge_requests)
72
+ .with(@git_repo,
73
+ :state => 'opened',
74
+ :source_branch => "#{@namespace}:#{@options[:branch]}",
75
+ :target_branch => 'master',
76
+ ).and_return([])
77
+
78
+ expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Attached the following labels to MR 42: HELLO, WORLD/).to_stdout
79
+ end
80
+ end
81
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe ModuleSync do
4
4
  context '::update' do
5
5
  it 'loads the managed modules from the specified :managed_modules_conf' do
6
- allow(ModuleSync).to receive(:local_files).and_return([])
6
+ allow(ModuleSync).to receive(:find_template_files).and_return([])
7
7
  allow(ModuleSync::Util).to receive(:parse_config).with('./config_defaults.yml').and_return({})
8
8
  expect(ModuleSync).to receive(:managed_modules).with('./test_file.yml', nil, nil).and_return([])
9
9
 
@@ -11,4 +11,12 @@ describe ModuleSync do
11
11
  ModuleSync.update(options)
12
12
  end
13
13
  end
14
+
15
+ context '::pr' do
16
+ describe "Raise Error" do
17
+ it 'raises an error when neither GITHUB_TOKEN nor GITLAB_TOKEN are set for PRs' do
18
+ expect { ModuleSync.pr({}) }.to raise_error(RuntimeError).and output(/No GitHub or GitLab token specified for --pr/).to_stderr
19
+ end
20
+ end
21
+ end
14
22
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vox Pupuli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-27 00:00:00.000000000 Z
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.14'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.14'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: gitlab
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: octokit
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -151,6 +165,7 @@ files:
151
165
  - ".travis.yml"
152
166
  - CHANGELOG.md
153
167
  - Gemfile
168
+ - HISTORY.md
154
169
  - LICENSE
155
170
  - README.md
156
171
  - Rakefile
@@ -166,12 +181,16 @@ files:
166
181
  - lib/modulesync/constants.rb
167
182
  - lib/modulesync/git.rb
168
183
  - lib/modulesync/hook.rb
184
+ - lib/modulesync/pr/github.rb
185
+ - lib/modulesync/pr/gitlab.rb
169
186
  - lib/modulesync/renderer.rb
170
187
  - lib/modulesync/settings.rb
171
188
  - lib/modulesync/util.rb
172
189
  - lib/monkey_patches.rb
173
190
  - modulesync.gemspec
174
191
  - spec/spec_helper.rb
192
+ - spec/unit/modulesync/pr/github_spec.rb
193
+ - spec/unit/modulesync/pr/gitlab_spec.rb
175
194
  - spec/unit/modulesync/settings_spec.rb
176
195
  - spec/unit/modulesync_spec.rb
177
196
  homepage: http://github.com/voxpupuli/modulesync
@@ -186,14 +205,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
205
  requirements:
187
206
  - - ">="
188
207
  - !ruby/object:Gem::Version
189
- version: 2.0.0
208
+ version: 2.5.0
190
209
  required_rubygems_version: !ruby/object:Gem::Requirement
191
210
  requirements:
192
211
  - - ">="
193
212
  - !ruby/object:Gem::Version
194
213
  version: '0'
195
214
  requirements: []
196
- rubygems_version: 3.0.1
215
+ rubygems_version: 3.1.2
197
216
  signing_key:
198
217
  specification_version: 4
199
218
  summary: Puppet Module Synchronizer
@@ -204,5 +223,7 @@ test_files:
204
223
  - features/support/env.rb
205
224
  - features/update.feature
206
225
  - spec/spec_helper.rb
226
+ - spec/unit/modulesync/pr/github_spec.rb
227
+ - spec/unit/modulesync/pr/gitlab_spec.rb
207
228
  - spec/unit/modulesync/settings_spec.rb
208
229
  - spec/unit/modulesync_spec.rb