modulesync 2.0.1 → 2.0.2
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.
- checksums.yaml +4 -4
- data/.config/cucumber.yml +1 -0
- data/.rubocop.yml +6 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -2
- data/README.md +4 -4
- data/features/cli.feature +11 -6
- data/features/hook.feature +3 -5
- data/features/step_definitions/git_steps.rb +63 -35
- data/features/support/env.rb +4 -0
- data/features/update.feature +142 -343
- data/features/update/bad_context.feature +26 -0
- data/lib/modulesync.rb +4 -4
- data/lib/modulesync/cli.rb +5 -4
- data/lib/modulesync/cli/thor.rb +24 -0
- data/lib/modulesync/git.rb +1 -1
- data/lib/monkey_patches.rb +9 -48
- data/modulesync.gemspec +2 -2
- data/spec/helpers/faker.rb +14 -0
- data/spec/helpers/faker/puppet_module_remote_repo.rb +140 -0
- metadata +12 -4
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Run `msync update` without a good context
|
2
|
+
|
3
|
+
Scenario: Run `msync update` without any module
|
4
|
+
Given a directory named "moduleroot"
|
5
|
+
When I run `msync update --message "In a bad context"`
|
6
|
+
Then the exit status should be 1
|
7
|
+
And the stderr should contain:
|
8
|
+
"""
|
9
|
+
No modules found
|
10
|
+
"""
|
11
|
+
|
12
|
+
Scenario: Run `msync update` without the "moduleroot" directory
|
13
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
14
|
+
When I run `msync update --message "In a bad context"`
|
15
|
+
Then the exit status should be 1
|
16
|
+
And the stderr should contain "moduleroot"
|
17
|
+
|
18
|
+
Scenario: Run `msync update` without commit message
|
19
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
20
|
+
And a directory named "moduleroot"
|
21
|
+
When I run `msync update`
|
22
|
+
Then the exit status should be 1
|
23
|
+
And the stderr should contain:
|
24
|
+
"""
|
25
|
+
No value provided for required option "--message"
|
26
|
+
"""
|
data/lib/modulesync.rb
CHANGED
@@ -39,10 +39,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
39
39
|
.collect { |p| p.chomp('.erb') }
|
40
40
|
.to_a
|
41
41
|
else
|
42
|
-
$
|
42
|
+
$stderr.puts "#{local_template_dir} does not exist." \
|
43
43
|
' Check that you are working in your module configs directory or' \
|
44
44
|
' that you have passed in the correct directory with -c.'
|
45
|
-
exit
|
45
|
+
exit 1
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -53,9 +53,9 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
53
53
|
def self.managed_modules(config_file, filter, negative_filter)
|
54
54
|
managed_modules = Util.parse_config(config_file)
|
55
55
|
if managed_modules.empty?
|
56
|
-
$
|
56
|
+
$stderr.puts "No modules found in #{config_file}." \
|
57
57
|
' Check that you specified the right :configs directory and :managed_modules_conf file.'
|
58
|
-
exit
|
58
|
+
exit 1
|
59
59
|
end
|
60
60
|
managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
|
61
61
|
managed_modules.reject! { |m| m =~ Regexp.new(negative_filter) } unless negative_filter.nil?
|
data/lib/modulesync/cli.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'thor'
|
2
|
+
|
2
3
|
require 'modulesync'
|
4
|
+
require 'modulesync/cli/thor'
|
3
5
|
require 'modulesync/constants'
|
4
6
|
require 'modulesync/util'
|
5
7
|
|
6
8
|
module ModuleSync
|
7
|
-
|
9
|
+
module CLI
|
8
10
|
def self.defaults
|
9
11
|
@defaults ||= Util.symbolize_keys(Util.parse_config(Constants::MODULESYNC_CONF_FILE))
|
10
12
|
end
|
@@ -36,15 +38,14 @@ module ModuleSync
|
|
36
38
|
class Base < Thor
|
37
39
|
class_option :project_root,
|
38
40
|
:aliases => '-c',
|
39
|
-
:desc => 'Path used by git to clone modules into.
|
41
|
+
:desc => 'Path used by git to clone modules into.',
|
40
42
|
:default => CLI.defaults[:project_root] || 'modules'
|
41
43
|
class_option :git_base,
|
42
44
|
:desc => 'Specify the base part of a git URL to pull from',
|
43
45
|
:default => CLI.defaults[:git_base] || 'git@github.com:'
|
44
46
|
class_option :namespace,
|
45
47
|
:aliases => '-n',
|
46
|
-
:desc => 'Remote github namespace (user or organization) to clone from and push to.'
|
47
|
-
' Defaults to puppetlabs',
|
48
|
+
:desc => 'Remote github namespace (user or organization) to clone from and push to.',
|
48
49
|
:default => CLI.defaults[:namespace] || 'puppetlabs'
|
49
50
|
class_option :filter,
|
50
51
|
:aliases => '-f',
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'modulesync/cli'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module CLI
|
6
|
+
# Workaround some, still unfixed, Thor behaviors
|
7
|
+
#
|
8
|
+
# This class extends ::Thor class to
|
9
|
+
# - exit with status code sets to `1` on Thor failure (e.g. missing required option)
|
10
|
+
# - exit with status code sets to `1` when user calls `msync` (or a subcommand) without required arguments
|
11
|
+
class Thor < ::Thor
|
12
|
+
desc '_invalid_command_call', 'Invalid command', hide: true
|
13
|
+
def _invalid_command_call
|
14
|
+
self.class.new.help
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
default_task :_invalid_command_call
|
18
|
+
|
19
|
+
def self.exit_on_failure?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/modulesync/git.rb
CHANGED
@@ -150,7 +150,7 @@ module ModuleSync
|
|
150
150
|
tag(repo, new, options[:tag_pattern]) if options[:tag]
|
151
151
|
end
|
152
152
|
rescue ::Git::GitExecuteError => git_error
|
153
|
-
if git_error.message
|
153
|
+
if git_error.message.match?(/working (directory|tree) clean/)
|
154
154
|
puts "There were no files to update in #{name}. Not committing."
|
155
155
|
return false
|
156
156
|
else
|
data/lib/monkey_patches.rb
CHANGED
@@ -1,55 +1,16 @@
|
|
1
1
|
module Git
|
2
|
-
|
3
|
-
# Monkey patch
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
}
|
11
|
-
final = {}
|
12
|
-
current_file = nil
|
13
|
-
full_diff_utf8_encoded = @full_diff.encode("UTF-8", "binary", {
|
14
|
-
:invalid => :replace,
|
15
|
-
:undef => :replace
|
16
|
-
})
|
17
|
-
full_diff_utf8_encoded.split("\n").each do |line|
|
18
|
-
if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
|
19
|
-
current_file = m[1]
|
20
|
-
final[current_file] = defaults.merge({:patch => line, :path => current_file})
|
21
|
-
elsif !current_file.nil?
|
22
|
-
if m = /^index (.......)\.\.(.......)( ......)*/.match(line)
|
23
|
-
final[current_file][:src] = m[1]
|
24
|
-
final[current_file][:dst] = m[2]
|
25
|
-
final[current_file][:mode] = m[3].strip if m[3]
|
26
|
-
end
|
27
|
-
if m = /^([[:alpha:]]*?) file mode (......)/.match(line)
|
28
|
-
final[current_file][:type] = m[1]
|
29
|
-
final[current_file][:mode] = m[2]
|
30
|
-
end
|
31
|
-
if m = /^Binary files /.match(line)
|
32
|
-
final[current_file][:binary] = true
|
33
|
-
end
|
34
|
-
final[current_file][:patch] << "\n" + line
|
35
|
-
end
|
36
|
-
end
|
37
|
-
final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
|
2
|
+
module LibMonkeyPatch
|
3
|
+
# Monkey patch set_custom_git_env_variables due to our ::Git::GitExecuteError handling.
|
4
|
+
#
|
5
|
+
# We rescue on the GitExecuteError and proceed differently based on the output of git.
|
6
|
+
# This way makes code language-dependent, so here we ensure that Git gem throw git commands with the "C" language
|
7
|
+
def set_custom_git_env_variables
|
8
|
+
super
|
9
|
+
ENV['LANG'] = 'C.UTF-8'
|
38
10
|
end
|
39
11
|
end
|
40
12
|
|
41
13
|
class Lib
|
42
|
-
|
43
|
-
def ls_files(location=nil)
|
44
|
-
location ||= '.'
|
45
|
-
hsh = {}
|
46
|
-
command_lines('ls-files', ['--stage', location]).each do |line|
|
47
|
-
(info, file) = line.split("\t")
|
48
|
-
(mode, sha, stage) = info.split
|
49
|
-
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
50
|
-
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
51
|
-
end
|
52
|
-
hsh
|
53
|
-
end
|
14
|
+
prepend LibMonkeyPatch
|
54
15
|
end
|
55
16
|
end
|
data/modulesync.gemspec
CHANGED
@@ -3,7 +3,7 @@ $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 = '2.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'
|
@@ -23,7 +23,7 @@ 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.
|
26
|
+
spec.add_runtime_dependency 'git', '~>1.7'
|
27
27
|
spec.add_runtime_dependency 'gitlab', '~>4.0'
|
28
28
|
spec.add_runtime_dependency 'octokit', '~>4.0'
|
29
29
|
spec.add_runtime_dependency 'puppet-blacksmith', '>= 3.0', '< 7'
|
@@ -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
|
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: 2.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:
|
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,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '1.
|
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.
|
96
|
+
version: '1.7'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: gitlab
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +164,7 @@ executables:
|
|
164
164
|
extensions: []
|
165
165
|
extra_rdoc_files: []
|
166
166
|
files:
|
167
|
+
- ".config/cucumber.yml"
|
167
168
|
- ".gitignore"
|
168
169
|
- ".rspec"
|
169
170
|
- ".rubocop.yml"
|
@@ -182,8 +183,10 @@ files:
|
|
182
183
|
- features/step_definitions/git_steps.rb
|
183
184
|
- features/support/env.rb
|
184
185
|
- features/update.feature
|
186
|
+
- features/update/bad_context.feature
|
185
187
|
- lib/modulesync.rb
|
186
188
|
- lib/modulesync/cli.rb
|
189
|
+
- lib/modulesync/cli/thor.rb
|
187
190
|
- lib/modulesync/constants.rb
|
188
191
|
- lib/modulesync/git.rb
|
189
192
|
- lib/modulesync/hook.rb
|
@@ -194,6 +197,8 @@ files:
|
|
194
197
|
- lib/modulesync/util.rb
|
195
198
|
- lib/monkey_patches.rb
|
196
199
|
- modulesync.gemspec
|
200
|
+
- spec/helpers/faker.rb
|
201
|
+
- spec/helpers/faker/puppet_module_remote_repo.rb
|
197
202
|
- spec/spec_helper.rb
|
198
203
|
- spec/unit/modulesync/pr/github_spec.rb
|
199
204
|
- spec/unit/modulesync/pr/gitlab_spec.rb
|
@@ -228,6 +233,9 @@ test_files:
|
|
228
233
|
- features/step_definitions/git_steps.rb
|
229
234
|
- features/support/env.rb
|
230
235
|
- features/update.feature
|
236
|
+
- features/update/bad_context.feature
|
237
|
+
- spec/helpers/faker.rb
|
238
|
+
- spec/helpers/faker/puppet_module_remote_repo.rb
|
231
239
|
- spec/spec_helper.rb
|
232
240
|
- spec/unit/modulesync/pr/github_spec.rb
|
233
241
|
- spec/unit/modulesync/pr/gitlab_spec.rb
|