modulesync 1.1.0 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/modulesync.gemspec CHANGED
@@ -3,14 +3,14 @@ $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 = '1.1.0'
6
+ spec.version = '2.0.2'
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) }
@@ -23,8 +23,9 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency 'rspec'
24
24
  spec.add_development_dependency 'rubocop', '~> 0.50.0'
25
25
 
26
- spec.add_runtime_dependency 'git', '~>1.3'
26
+ spec.add_runtime_dependency 'git', '~>1.7'
27
+ spec.add_runtime_dependency 'gitlab', '~>4.0'
27
28
  spec.add_runtime_dependency 'octokit', '~>4.0'
28
- spec.add_runtime_dependency 'puppet-blacksmith', '~>3.0'
29
+ spec.add_runtime_dependency 'puppet-blacksmith', '>= 3.0', '< 7'
29
30
  spec.add_runtime_dependency 'thor'
30
31
  end
@@ -0,0 +1,14 @@
1
+ module ModuleSync
2
+ # Faker is a top-level module to keep global faker config
3
+ module Faker
4
+ def self.working_directory=(path)
5
+ @working_directory = path
6
+ end
7
+
8
+ def self.working_directory
9
+ raise 'Working directory must be set' if @working_directory.nil?
10
+ FileUtils.mkdir_p @working_directory
11
+ @working_directory
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,140 @@
1
+ require 'open3'
2
+
3
+ require_relative '../faker'
4
+
5
+ module ModuleSync
6
+ # Fake a remote git repository that holds a puppet module
7
+ #
8
+ # This module allows to fake a remote repositiory using:
9
+ # - a bare repo
10
+ # - a temporary cloned repo to operate on the remote before exposing to modulesync
11
+ #
12
+ # Note: This module needs to have working_directory sets before using it
13
+ module Faker
14
+ class PuppetModuleRemoteRepo
15
+ class CommandExecutionError < StandardError; end
16
+
17
+ attr_reader :name, :namespace
18
+
19
+ def initialize(name, namespace)
20
+ @name = name
21
+ @namespace = namespace
22
+ end
23
+
24
+ def populate
25
+ FileUtils.chdir(Faker.working_directory) do
26
+ run %W[git init --bare #{bare_repo_dir}]
27
+ run %W[git clone #{bare_repo_dir} #{tmp_repo_dir}]
28
+
29
+ module_short_name = name.split('-').last
30
+
31
+ FileUtils.chdir(tmp_repo_dir) do
32
+ metadata = {
33
+ name: "modulesync-#{module_short_name}",
34
+ version: '0.4.2',
35
+ author: 'ModuleSync team',
36
+ }
37
+
38
+ File.write 'metadata.json', metadata.to_json
39
+ run %w[git add metadata.json]
40
+ run %w[git commit --message] << 'Initial commit'
41
+ run %w[git push]
42
+ end
43
+ end
44
+ end
45
+
46
+ def read_only=(value)
47
+ mode = value ? '0444' : '0644'
48
+ FileUtils.chdir(bare_repo_dir) do
49
+ run %W[git config core.sharedRepository #{mode}]
50
+ end
51
+ end
52
+
53
+ def default_branch
54
+ FileUtils.chdir(bare_repo_dir) do
55
+ stdout = run %w[git symbolic-ref --short HEAD]
56
+ return stdout.chomp
57
+ end
58
+ end
59
+
60
+ def default_branch=(value)
61
+ FileUtils.chdir(bare_repo_dir) do
62
+ run %W[git branch -M #{default_branch} #{value}]
63
+ run %W[git symbolic-ref HEAD refs/heads/#{value}]
64
+ end
65
+ end
66
+
67
+ def read_file(filename, branch = nil)
68
+ branch ||= default_branch
69
+ FileUtils.chdir(bare_repo_dir) do
70
+ return run %W[git show #{branch}:#{filename}]
71
+ rescue CommandExecutionError
72
+ return nil
73
+ end
74
+ end
75
+
76
+ def add_file(filename, content, branch = nil)
77
+ branch ||= default_branch
78
+ FileUtils.chdir(tmp_repo_dir) do
79
+ run %W[git checkout #{branch}]
80
+ File.write filename, content
81
+ run %W[git add #{filename}]
82
+ run %w[git commit --message] << "Add file: '#{filename}'"
83
+ run %w[git push]
84
+ end
85
+ end
86
+
87
+ def commit_count_between(commit1, commit2)
88
+ FileUtils.chdir(bare_repo_dir) do
89
+ stdout = run %W[git rev-list --count #{commit1}..#{commit2}]
90
+ return Integer(stdout)
91
+ end
92
+ end
93
+
94
+ def commit_count_by(author, commit = nil)
95
+ FileUtils.chdir(bare_repo_dir) do
96
+ commit ||= '--all'
97
+ stdout = run %W[git rev-list #{commit} --author #{author} --count]
98
+ return Integer(stdout)
99
+ end
100
+ end
101
+
102
+ def remote_url
103
+ "file://#{bare_repo_dir}"
104
+ end
105
+
106
+ def self.git_base
107
+ "file://#{Faker.working_directory}/bare/"
108
+ end
109
+
110
+ private
111
+
112
+ def tmp_repo_dir
113
+ File.join Faker.working_directory, 'tmp', namespace, name
114
+ end
115
+
116
+ def bare_repo_dir
117
+ File.join Faker.working_directory, 'bare', namespace, "#{name}.git"
118
+ end
119
+
120
+ GIT_ENV = {
121
+ 'GIT_AUTHOR_NAME' => 'Faker',
122
+ 'GIT_AUTHOR_EMAIL' => 'faker@example.com',
123
+ 'GIT_COMMITTER_NAME' => 'Faker',
124
+ 'GIT_COMMITTER_EMAIL' => 'faker@example.com',
125
+ }.freeze
126
+
127
+ def run(command)
128
+ stdout, stderr, status = Open3.capture3(GIT_ENV, *command)
129
+ return stdout if status.success?
130
+
131
+ warn "Command '#{command}' failed: #{status}"
132
+ warn ' STDOUT:'
133
+ warn stdout
134
+ warn ' STDERR:'
135
+ warn stderr
136
+ raise CommandExecutionError, "Command '#{command}' failed: #{status}"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -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
@@ -12,58 +12,11 @@ describe ModuleSync do
12
12
  end
13
13
  end
14
14
 
15
- context '::manage_pr' do
16
- before(:each) do
17
- stub_const('GITHUB_TOKEN', 'test')
18
- @git_repo = 'test/modulesync'
19
- @namespace, @repo_name = @git_repo.split('/')
20
- @options = {
21
- :pr => true,
22
- :pr_title => 'Test PR is submitted',
23
- :branch => 'test',
24
- :message => 'Hello world',
25
- :pr_auto_merge => false,
26
- }
27
-
28
- @client = double()
29
- end
30
-
31
- it 'rasies an error when GITHUB_TOKEN not set for PRs' do
32
- stub_const('GITHUB_TOKEN', '')
33
- options = {:pr => true, :skip_broken => false}
34
-
35
- expect { ModuleSync.manage_pr(@namespace, @repo_name, options) }.to raise_error(RuntimeError).and output(/GITHUB_TOKEN/).to_stderr
36
- end
37
-
38
- it 'submits PR when --pr is set' do
39
- allow(Octokit::Client).to receive(:new).and_return(@client)
40
- allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])
41
- 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"})
42
- expect { ModuleSync.manage_pr(@namespace, @repo_name, @options) }.to output(/Submitted PR/).to_stdout
43
- end
44
-
45
- it 'skips submitting PR if one has already been issued' do
46
- allow(Octokit::Client).to receive(:new).and_return(@client)
47
-
48
- pr = {
49
- "title" => "Test title",
50
- "html_url" => "https://example.com/pulls/44",
51
- "number" => "44"
52
- }
53
-
54
- expect(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([pr])
55
- expect { ModuleSync.manage_pr(@namespace, @repo_name, @options) }.to output(/Skipped! 1 PRs found for branch test/).to_stdout
56
- end
57
-
58
- it 'adds labels to PR when --pr-labels is set' do
59
- @options[:pr_labels] = "HELLO,WORLD"
60
-
61
- allow(Octokit::Client).to receive(:new).and_return(@client)
62
- allow(@client).to receive(:create_pull_request).and_return({"html_url" => "http://example.com/pulls/22", "number" => "44"})
63
- allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])
64
-
65
- expect(@client).to receive(:add_labels_to_an_issue).with(@git_repo, "44", ["HELLO", "WORLD"])
66
- expect { ModuleSync.manage_pr(@namespace, @repo_name, @options) }.to output(/Attaching the following labels to PR 44: HELLO, WORLD/).to_stdout
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
67
20
  end
68
21
  end
69
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vox Pupuli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-04 00:00:00.000000000 Z
11
+ date: 2021-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
@@ -86,14 +86,28 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.3'
89
+ version: '1.7'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.3'
96
+ version: '1.7'
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
@@ -112,16 +126,22 @@ dependencies:
112
126
  name: puppet-blacksmith
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
- - - "~>"
129
+ - - ">="
116
130
  - !ruby/object:Gem::Version
117
131
  version: '3.0'
132
+ - - "<"
133
+ - !ruby/object:Gem::Version
134
+ version: '7'
118
135
  type: :runtime
119
136
  prerelease: false
120
137
  version_requirements: !ruby/object:Gem::Requirement
121
138
  requirements:
122
- - - "~>"
139
+ - - ">="
123
140
  - !ruby/object:Gem::Version
124
141
  version: '3.0'
142
+ - - "<"
143
+ - !ruby/object:Gem::Version
144
+ version: '7'
125
145
  - !ruby/object:Gem::Dependency
126
146
  name: thor
127
147
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +164,7 @@ executables:
144
164
  extensions: []
145
165
  extra_rdoc_files: []
146
166
  files:
167
+ - ".config/cucumber.yml"
147
168
  - ".gitignore"
148
169
  - ".rspec"
149
170
  - ".rubocop.yml"
@@ -151,6 +172,7 @@ files:
151
172
  - ".travis.yml"
152
173
  - CHANGELOG.md
153
174
  - Gemfile
175
+ - HISTORY.md
154
176
  - LICENSE
155
177
  - README.md
156
178
  - Rakefile
@@ -161,17 +183,25 @@ files:
161
183
  - features/step_definitions/git_steps.rb
162
184
  - features/support/env.rb
163
185
  - features/update.feature
186
+ - features/update/bad_context.feature
164
187
  - lib/modulesync.rb
165
188
  - lib/modulesync/cli.rb
189
+ - lib/modulesync/cli/thor.rb
166
190
  - lib/modulesync/constants.rb
167
191
  - lib/modulesync/git.rb
168
192
  - lib/modulesync/hook.rb
193
+ - lib/modulesync/pr/github.rb
194
+ - lib/modulesync/pr/gitlab.rb
169
195
  - lib/modulesync/renderer.rb
170
196
  - lib/modulesync/settings.rb
171
197
  - lib/modulesync/util.rb
172
198
  - lib/monkey_patches.rb
173
199
  - modulesync.gemspec
200
+ - spec/helpers/faker.rb
201
+ - spec/helpers/faker/puppet_module_remote_repo.rb
174
202
  - spec/spec_helper.rb
203
+ - spec/unit/modulesync/pr/github_spec.rb
204
+ - spec/unit/modulesync/pr/gitlab_spec.rb
175
205
  - spec/unit/modulesync/settings_spec.rb
176
206
  - spec/unit/modulesync_spec.rb
177
207
  homepage: http://github.com/voxpupuli/modulesync
@@ -186,7 +216,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
216
  requirements:
187
217
  - - ">="
188
218
  - !ruby/object:Gem::Version
189
- version: 2.0.0
219
+ version: 2.5.0
190
220
  required_rubygems_version: !ruby/object:Gem::Requirement
191
221
  requirements:
192
222
  - - ">="
@@ -203,6 +233,11 @@ test_files:
203
233
  - features/step_definitions/git_steps.rb
204
234
  - features/support/env.rb
205
235
  - features/update.feature
236
+ - features/update/bad_context.feature
237
+ - spec/helpers/faker.rb
238
+ - spec/helpers/faker/puppet_module_remote_repo.rb
206
239
  - spec/spec_helper.rb
240
+ - spec/unit/modulesync/pr/github_spec.rb
241
+ - spec/unit/modulesync/pr/gitlab_spec.rb
207
242
  - spec/unit/modulesync/settings_spec.rb
208
243
  - spec/unit/modulesync_spec.rb