modulesync 2.2.0 → 2.3.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +18 -3
  3. data/.github/workflows/release.yml +2 -4
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +14 -8
  6. data/.rubocop_todo.yml +25 -17
  7. data/.simplecov +46 -0
  8. data/CHANGELOG.md +32 -0
  9. data/Gemfile +1 -1
  10. data/bin/msync +17 -1
  11. data/features/cli.feature +12 -6
  12. data/features/execute.feature +51 -0
  13. data/features/hook.feature +5 -8
  14. data/features/push.feature +46 -0
  15. data/features/reset.feature +57 -0
  16. data/features/step_definitions/git_steps.rb +29 -1
  17. data/features/support/env.rb +9 -0
  18. data/features/update/bump_version.feature +8 -12
  19. data/features/update/dot_sync.feature +52 -0
  20. data/features/update/pull_request.feature +180 -0
  21. data/features/update.feature +74 -103
  22. data/lib/modulesync/cli/thor.rb +12 -0
  23. data/lib/modulesync/cli.rb +122 -28
  24. data/lib/modulesync/git_service/base.rb +63 -0
  25. data/lib/modulesync/git_service/factory.rb +28 -0
  26. data/lib/modulesync/{pr → git_service}/github.rb +23 -21
  27. data/lib/modulesync/git_service/gitlab.rb +62 -0
  28. data/lib/modulesync/git_service.rb +96 -0
  29. data/lib/modulesync/hook.rb +11 -13
  30. data/lib/modulesync/renderer.rb +3 -6
  31. data/lib/modulesync/repository.rb +71 -25
  32. data/lib/modulesync/settings.rb +0 -1
  33. data/lib/modulesync/source_code.rb +28 -2
  34. data/lib/modulesync/util.rb +4 -4
  35. data/lib/modulesync.rb +104 -66
  36. data/modulesync.gemspec +7 -4
  37. data/spec/helpers/faker/puppet_module_remote_repo.rb +16 -1
  38. data/spec/spec_helper.rb +1 -23
  39. data/spec/unit/modulesync/git_service/factory_spec.rb +16 -0
  40. data/spec/unit/modulesync/git_service/github_spec.rb +81 -0
  41. data/spec/unit/modulesync/git_service/gitlab_spec.rb +90 -0
  42. data/spec/unit/modulesync/git_service_spec.rb +201 -0
  43. data/spec/unit/modulesync/source_code_spec.rb +22 -0
  44. data/spec/unit/modulesync_spec.rb +0 -12
  45. metadata +74 -12
  46. data/lib/modulesync/pr/gitlab.rb +0 -54
  47. data/spec/unit/modulesync/pr/github_spec.rb +0 -49
  48. data/spec/unit/modulesync/pr/gitlab_spec.rb +0 -81
@@ -7,31 +7,34 @@ require 'modulesync/util'
7
7
 
8
8
  module ModuleSync
9
9
  module CLI
10
+ def self.prepare_options(cli_options, **more_options)
11
+ options = CLI.defaults
12
+ options.merge! Util.symbolize_keys(cli_options)
13
+ options.merge! more_options
14
+
15
+ Util.symbolize_keys options
16
+ end
17
+
10
18
  def self.defaults
11
19
  @defaults ||= Util.symbolize_keys(Util.parse_config(Constants::MODULESYNC_CONF_FILE))
12
20
  end
13
21
 
14
22
  class Hook < Thor
15
- class_option :project_root,
16
- :aliases => '-c',
17
- :desc => 'Path used by git to clone modules into. Defaults to "modules"',
18
- :default => CLI.defaults[:project_root] || 'modules'
19
- class_option :hook_args,
20
- :aliases => '-a',
21
- :desc => 'Arguments to pass to msync in the git hook'
22
-
23
+ option :hook_args,
24
+ :aliases => '-a',
25
+ :desc => 'Arguments to pass to msync in the git hook'
26
+ option :branch,
27
+ :aliases => '-b',
28
+ :desc => 'Branch name to pass to msync in the git hook',
29
+ :default => CLI.defaults[:branch]
23
30
  desc 'activate', 'Activate the git hook.'
24
31
  def activate
25
- config = { :command => 'hook' }.merge(options)
26
- config[:hook] = 'activate'
27
- ModuleSync.hook(config)
32
+ ModuleSync.hook CLI.prepare_options(options, hook: 'activate')
28
33
  end
29
34
 
30
35
  desc 'deactivate', 'Deactivate the git hook.'
31
36
  def deactivate
32
- config = { :command => 'hook' }.merge(options)
33
- config[:hook] = 'deactivate'
34
- ModuleSync.hook(config)
37
+ ModuleSync.hook CLI.prepare_options(options, hook: 'deactivate')
35
38
  end
36
39
  end
37
40
 
@@ -39,7 +42,7 @@ module ModuleSync
39
42
  class_option :project_root,
40
43
  :aliases => '-c',
41
44
  :desc => 'Path used by git to clone modules into.',
42
- :default => CLI.defaults[:project_root] || 'modules'
45
+ :default => CLI.defaults[:project_root]
43
46
  class_option :git_base,
44
47
  :desc => 'Specify the base part of a git URL to pull from',
45
48
  :default => CLI.defaults[:git_base] || 'git@github.com:'
@@ -53,11 +56,11 @@ module ModuleSync
53
56
  class_option :negative_filter,
54
57
  :aliases => '-x',
55
58
  :desc => 'A regular expression to skip repositories.'
56
- class_option :branch,
57
- :aliases => '-b',
58
- :desc => 'Branch name to make the changes in.' \
59
- ' Defaults to the default branch of the upstream repository, but falls back to "master".',
60
- :default => CLI.defaults[:branch]
59
+ class_option :verbose,
60
+ :aliases => '-v',
61
+ :desc => 'Verbose mode',
62
+ :type => :boolean,
63
+ :default => false
61
64
 
62
65
  desc 'update', 'Update the modules in managed_modules.yml'
63
66
  option :message,
@@ -67,7 +70,7 @@ module ModuleSync
67
70
  option :configs,
68
71
  :aliases => '-c',
69
72
  :desc => 'The local directory or remote repository to define the list of managed modules,' \
70
- ' the file templates, and the default values for template variables.'
73
+ ' the file templates, and the default values for template variables.'
71
74
  option :managed_modules_conf,
72
75
  :desc => 'The file name to define the list of managed modules'
73
76
  option :remote_branch,
@@ -104,7 +107,7 @@ module ModuleSync
104
107
  :default => CLI.defaults[:pr_labels] || []
105
108
  option :pr_target_branch,
106
109
  :desc => 'Target branch for the pull/merge request',
107
- :default => CLI.defaults[:pr_target_branch] || 'master'
110
+ :default => CLI.defaults[:pr_target_branch]
108
111
  option :offline,
109
112
  :type => :boolean,
110
113
  :desc => 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
@@ -130,17 +133,108 @@ module ModuleSync
130
133
  :type => :boolean,
131
134
  :aliases => '-F',
132
135
  :desc => 'Produce a failure exit code when there are warnings' \
133
- ' (only has effect when --skip_broken is enabled)',
136
+ ' (only has effect when --skip_broken is enabled)',
134
137
  :default => false
135
-
138
+ option :branch,
139
+ :aliases => '-b',
140
+ :desc => 'Branch name to make the changes in.' \
141
+ ' Defaults to the default branch of the upstream repository, but falls back to "master".',
142
+ :default => CLI.defaults[:branch]
136
143
  def update
137
- config = { :command => 'update' }.merge(options)
138
- config = Util.symbolize_keys(config)
144
+ config = CLI.prepare_options(options)
139
145
  raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop] \
140
146
  || config[:message] \
141
147
  || config[:offline]
142
- config[:git_opts] = { 'amend' => config[:amend], 'force' => config[:force] }
143
- ModuleSync.update(config)
148
+
149
+ ModuleSync.update config
150
+ end
151
+
152
+ desc 'execute [OPTIONS] -- COMMAND..', 'Execute the command in each managed modules'
153
+ long_desc <<~DESC
154
+ Execute the command in each managed modules.
155
+
156
+ COMMAND can be an absolute or a relative path.
157
+
158
+ To ease running local commands, a relative path is expanded with the current user directory but only if the target file exists.
159
+
160
+ Example: `msync exec custom-scripts/true` will run "$PWD/custom-scripts/true" in each repository.
161
+
162
+ As side effect, you can shadow system binary if a local file is present:
163
+ \x5 `msync exec true` will run "$PWD/true", not `/bin/true` if "$PWD/true" exists.
164
+ DESC
165
+
166
+ option :configs,
167
+ :aliases => '-c',
168
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
169
+ ' the file templates, and the default values for template variables.'
170
+ option :managed_modules_conf,
171
+ :desc => 'The file name to define the list of managed modules'
172
+ option :branch,
173
+ :aliases => '-b',
174
+ :desc => 'Branch name to make the changes in.',
175
+ :default => CLI.defaults[:branch]
176
+ option :fail_fast,
177
+ :type => :boolean,
178
+ :desc => 'Abort the run after a command execution failure',
179
+ :default => CLI.defaults[:fail_fast].nil? ? true : CLI.defaults[:fail_fast]
180
+ def execute(*command_args)
181
+ raise Thor::Error, 'COMMAND is a required argument' if command_args.empty?
182
+
183
+ ModuleSync.execute CLI.prepare_options(options, command_args: command_args)
184
+ end
185
+
186
+ desc 'reset', 'Reset local repositories to a well-known state'
187
+ long_desc <<~DESC
188
+ Reset local repository to a well-known state:
189
+ \x5 * Switch local repositories to specified branch
190
+ \x5 * Fetch and prune repositories unless running with `--offline` option
191
+ \x5 * Hard-reset any changes to specified source branch, technically any git refs, e.g. `main`, `origin/wip`
192
+ \x5 * Clean all extra local files
193
+
194
+ Note: If a repository is not already cloned, it will operate the following to reach to well-known state:
195
+ \x5 * Clone the repository
196
+ \x5 * Switch to specified branch
197
+ DESC
198
+ option :configs,
199
+ :aliases => '-c',
200
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
201
+ ' the file templates, and the default values for template variables.'
202
+ option :managed_modules_conf,
203
+ :desc => 'The file name to define the list of managed modules'
204
+ option :branch,
205
+ :aliases => '-b',
206
+ :desc => 'Branch name to make the changes in.',
207
+ :default => CLI.defaults[:branch]
208
+ option :offline,
209
+ :type => :boolean,
210
+ :desc => 'Only proceed local operations',
211
+ :default => false
212
+ option :source_branch,
213
+ :desc => 'Branch to reset from (e.g. origin/wip)'
214
+ def reset
215
+ ModuleSync.reset CLI.prepare_options(options)
216
+ end
217
+
218
+ desc 'push', 'Push all available commits from branch to remote'
219
+ option :configs,
220
+ :aliases => '-c',
221
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
222
+ ' the file templates, and the default values for template variables.'
223
+ option :managed_modules_conf,
224
+ :desc => 'The file name to define the list of managed modules'
225
+ option :branch,
226
+ :aliases => '-b',
227
+ :desc => 'Branch name to push',
228
+ :default => CLI.defaults[:branch]
229
+ option :remote_branch,
230
+ :desc => 'Remote branch to push to (e.g. maintenance)'
231
+ def push
232
+ ModuleSync.push CLI.prepare_options(options)
233
+ end
234
+
235
+ desc 'clone', 'Clone repositories that need to'
236
+ def clone
237
+ ModuleSync.clone CLI.prepare_options(options)
144
238
  end
145
239
 
146
240
  desc 'hook', 'Activate or deactivate a git hook.'
@@ -0,0 +1,63 @@
1
+ module ModuleSync
2
+ module GitService
3
+ # Generic class for git services
4
+ class Base
5
+ def open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
6
+ unless source_branch != target_branch
7
+ raise ModuleSync::Error,
8
+ "Unable to open a pull request with the same source and target branch: '#{source_branch}'"
9
+ end
10
+
11
+ _open_pull_request(
12
+ repo_path: repo_path,
13
+ namespace: namespace,
14
+ title: title,
15
+ message: message,
16
+ source_branch: source_branch,
17
+ target_branch: target_branch,
18
+ labels: labels,
19
+ noop: noop,
20
+ )
21
+ end
22
+
23
+ # This method attempts to guess the git service endpoint based on remote
24
+ def self.guess_endpoint_from(remote:)
25
+ hostname = extract_hostname(remote)
26
+ return nil if hostname.nil?
27
+
28
+ "https://#{hostname}"
29
+ end
30
+
31
+ # This method extracts hostname from URL like:
32
+ #
33
+ # - ssh://[user@]host.xz[:port]/path/to/repo.git/
34
+ # - git://host.xz[:port]/path/to/repo.git/
35
+ # - [user@]host.xz:path/to/repo.git/
36
+ # - http[s]://host.xz[:port]/path/to/repo.git/
37
+ # - ftp[s]://host.xz[:port]/path/to/repo.git/
38
+ #
39
+ # Returns nil if
40
+ # - /path/to/repo.git/
41
+ # - file:///path/to/repo.git/
42
+ # - any invalid URL
43
+ def self.extract_hostname(url)
44
+ return nil if url.start_with?('/') || url.start_with?('file://') # local path (e.g. file:///path/to/repo)
45
+
46
+ unless url.start_with?(%r{[a-z]+://}) # SSH notation does not contain protocol (e.g. user@server:path/to/repo/)
47
+ pattern = /^(?<user>.*@)?(?<hostname>[\w|.]*):(?<repo>.*)$/ # SSH path (e.g. user@server:repo)
48
+ return url.match(pattern)[:hostname] if url.match?(pattern)
49
+ end
50
+
51
+ URI.parse(url).host
52
+ rescue URI::InvalidURIError
53
+ nil
54
+ end
55
+
56
+ protected
57
+
58
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
59
+ raise NotImplementedError
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ module ModuleSync
2
+ module GitService
3
+ # Git service's factory
4
+ module Factory
5
+ def self.instantiate(type:, endpoint:, token:)
6
+ raise MissingCredentialsError, <<~MESSAGE if token.nil?
7
+ A token is required to use services from #{type}:
8
+ Please set environment variable: "#{type.upcase}_TOKEN" or set the token entry in module options.
9
+ MESSAGE
10
+
11
+ klass(type: type).new token, endpoint
12
+ end
13
+
14
+ def self.klass(type:)
15
+ case type
16
+ when :github
17
+ require 'modulesync/git_service/github'
18
+ ModuleSync::GitService::GitHub
19
+ when :gitlab
20
+ require 'modulesync/git_service/gitlab'
21
+ ModuleSync::GitService::GitLab
22
+ else
23
+ raise NotImplementedError, "Unknown git service: '#{type}'"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,28 +1,30 @@
1
+ require 'modulesync/git_service'
2
+ require 'modulesync/git_service/base'
1
3
  require 'octokit'
2
- require 'modulesync/util'
3
4
 
4
5
  module ModuleSync
5
- module PR
6
+ module GitService
6
7
  # GitHub creates and manages pull requests on github.com or GitHub
7
8
  # Enterprise installations.
8
- class GitHub
9
+ class GitHub < Base
9
10
  def initialize(token, endpoint)
11
+ super()
12
+
10
13
  Octokit.configure do |c|
11
14
  c.api_endpoint = endpoint
12
15
  end
13
16
  @api = Octokit::Client.new(:access_token => token)
14
17
  end
15
18
 
16
- def manage(namespace, module_name, options)
17
- repo_path = File.join(namespace, module_name)
18
- branch = options[:remote_branch] || options[:branch]
19
- head = "#{namespace}:#{branch}"
20
- target_branch = options[:pr_target_branch] || 'master'
19
+ private
20
+
21
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
22
+ head = "#{namespace}:#{source_branch}"
21
23
 
22
- if options[:noop]
24
+ if noop
23
25
  $stdout.puts \
24
- "Using no-op. Would submit PR '#{options[:pr_title]}' to #{repo_path} " \
25
- "- merges #{branch} into #{target_branch}"
26
+ "Using no-op. Would submit PR '#{title}' to '#{repo_path}' " \
27
+ "- merges '#{source_branch}' into '#{target_branch}'"
26
28
  return
27
29
  end
28
30
 
@@ -32,25 +34,25 @@ module ModuleSync
32
34
  :head => head)
33
35
  unless pull_requests.empty?
34
36
  # Skip creating the PR if it exists already.
35
- $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{branch}"
37
+ $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch '#{source_branch}'"
36
38
  return
37
39
  end
38
40
 
39
- pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
40
41
  pr = @api.create_pull_request(repo_path,
41
42
  target_branch,
42
- branch,
43
- options[:pr_title],
44
- options[:message])
43
+ source_branch,
44
+ title,
45
+ message)
45
46
  $stdout.puts \
46
- "Submitted PR '#{options[:pr_title]}' to #{repo_path} " \
47
- "- merges #{branch} into #{target_branch}"
47
+ "Submitted PR '#{title}' to '#{repo_path}' " \
48
+ "- merges #{source_branch} into #{target_branch}"
48
49
 
49
50
  # We only assign labels to the PR if we've discovered a list > 1. The labels MUST
50
51
  # already exist. We DO NOT create missing labels.
51
- return if pr_labels.empty?
52
- $stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
53
- @api.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
52
+ return if labels.empty?
53
+
54
+ $stdout.puts "Attaching the following labels to PR #{pr['number']}: #{labels.join(', ')}"
55
+ @api.add_labels_to_an_issue(repo_path, pr['number'], labels)
54
56
  end
55
57
  end
56
58
  end
@@ -0,0 +1,62 @@
1
+ require 'gitlab'
2
+ require 'modulesync/git_service'
3
+ require 'modulesync/git_service/base'
4
+
5
+ module ModuleSync
6
+ module GitService
7
+ # GitLab creates and manages merge requests on gitlab.com or private GitLab
8
+ # installations.
9
+ class GitLab < Base
10
+ def initialize(token, endpoint)
11
+ super()
12
+
13
+ @api = Gitlab::Client.new(
14
+ :endpoint => endpoint,
15
+ :private_token => token,
16
+ )
17
+ end
18
+
19
+ def self.guess_endpoint_from(remote:)
20
+ endpoint = super
21
+ return nil if endpoint.nil?
22
+
23
+ endpoint += '/api/v4'
24
+ endpoint
25
+ end
26
+
27
+ private
28
+
29
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
30
+ if noop
31
+ $stdout.puts \
32
+ "Using no-op. Would submit MR '#{title}' to '#{repo_path}' " \
33
+ "- merges #{source_branch} into #{target_branch}"
34
+ return
35
+ end
36
+
37
+ merge_requests = @api.merge_requests(repo_path,
38
+ :state => 'opened',
39
+ :source_branch => source_branch,
40
+ :target_branch => target_branch)
41
+ unless merge_requests.empty?
42
+ # Skip creating the MR if it exists already.
43
+ $stdout.puts "Skipped! #{merge_requests.length} MRs found for branch '#{source_branch}'"
44
+ return
45
+ end
46
+
47
+ mr = @api.create_merge_request(repo_path,
48
+ title,
49
+ :source_branch => source_branch,
50
+ :target_branch => target_branch,
51
+ :labels => labels)
52
+ $stdout.puts \
53
+ "Submitted MR '#{title}' to '#{repo_path}' " \
54
+ "- merges '#{source_branch}' into '#{target_branch}'"
55
+
56
+ return if labels.empty?
57
+
58
+ $stdout.puts "Attached the following labels to MR #{mr.iid}: #{labels.join(', ')}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,96 @@
1
+ module ModuleSync
2
+ class Error < StandardError; end
3
+
4
+ # Namespace for Git service classes (ie. GitHub, GitLab)
5
+ module GitService
6
+ class MissingCredentialsError < Error; end
7
+
8
+ class UnguessableTypeError < Error; end
9
+
10
+ def self.configuration_for(sourcecode:)
11
+ type = type_for(sourcecode: sourcecode)
12
+
13
+ {
14
+ type: type,
15
+ endpoint: endpoint_for(sourcecode: sourcecode, type: type),
16
+ token: token_for(sourcecode: sourcecode, type: type),
17
+ }
18
+ end
19
+
20
+ # This method attempts to guess git service's type (ie. gitlab or github)
21
+ # It process in this order
22
+ # 1. use module specific configuration entry (ie. a specific entry named `gitlab` or `github`)
23
+ # 2. guess using remote url (ie. looking for `github` or `gitlab` string)
24
+ # 3. use environment variables (ie. check if GITHUB_TOKEN or GITLAB_TOKEN is set)
25
+ # 4. fail
26
+ def self.type_for(sourcecode:)
27
+ return :github unless sourcecode.options[:github].nil?
28
+ return :gitlab unless sourcecode.options[:gitlab].nil?
29
+ return :github if sourcecode.repository_remote.include? 'github'
30
+ return :gitlab if sourcecode.repository_remote.include? 'gitlab'
31
+
32
+ if ENV['GITLAB_TOKEN'].nil? && ENV['GITHUB_TOKEN'].nil?
33
+ raise UnguessableTypeError, <<~MESSAGE
34
+ Unable to guess Git service type without GITLAB_TOKEN or GITHUB_TOKEN sets.
35
+ MESSAGE
36
+ end
37
+
38
+ unless ENV['GITLAB_TOKEN'].nil? || ENV['GITHUB_TOKEN'].nil?
39
+ raise UnguessableTypeError, <<~MESSAGE
40
+ Unable to guess Git service type with both GITLAB_TOKEN and GITHUB_TOKEN sets.
41
+
42
+ Please set the wanted one in configuration (ie. add `gitlab:` or `github:` key)
43
+ MESSAGE
44
+ end
45
+
46
+ return :github unless ENV['GITHUB_TOKEN'].nil?
47
+ return :gitlab unless ENV['GITLAB_TOKEN'].nil?
48
+
49
+ raise NotImplementedError
50
+ end
51
+
52
+ # This method attempts to find git service's endpoint based on sourcecode and type
53
+ # It process in this order
54
+ # 1. use module specific configuration (ie. `base_url`)
55
+ # 2. use environment variable dependending on type (e.g. GITLAB_BASE_URL)
56
+ # 3. guess using the git remote url
57
+ # 4. fail
58
+ def self.endpoint_for(sourcecode:, type:)
59
+ endpoint = sourcecode.options.dig(type, :base_url)
60
+
61
+ endpoint ||= case type
62
+ when :github
63
+ ENV['GITHUB_BASE_URL']
64
+ when :gitlab
65
+ ENV['GITLAB_BASE_URL']
66
+ end
67
+
68
+ endpoint ||= GitService::Factory.klass(type: type).guess_endpoint_from(remote: sourcecode.repository_remote)
69
+
70
+ raise NotImplementedError, <<~MESSAGE if endpoint.nil?
71
+ Unable to guess endpoint for remote: '#{sourcecode.repository_remote}'
72
+ Please provide `base_url` option in configuration file
73
+ MESSAGE
74
+
75
+ endpoint
76
+ end
77
+
78
+ # This method attempts to find the token associated to provided sourcecode and type
79
+ # It process in this order:
80
+ # 1. use module specific configuration (ie. `token`)
81
+ # 2. use environment variable depending on type (e.g. GITLAB_TOKEN)
82
+ # 3. fail
83
+ def self.token_for(sourcecode:, type:)
84
+ token = sourcecode.options.dig(type, :token)
85
+
86
+ token ||= case type
87
+ when :github
88
+ ENV['GITHUB_TOKEN']
89
+ when :gitlab
90
+ ENV['GITLAB_TOKEN']
91
+ end
92
+
93
+ token
94
+ end
95
+ end
96
+ end
@@ -6,20 +6,20 @@ module ModuleSync
6
6
 
7
7
  def initialize(hook_file, options = [])
8
8
  @hook_file = hook_file
9
- @namespace = options['namespace']
10
- @branch = options['branch']
11
- @args = options['hook_args']
9
+ @namespace = options[:namespace]
10
+ @branch = options[:branch]
11
+ @args = options[:hook_args]
12
12
  end
13
13
 
14
14
  def content(arguments)
15
- <<-CONTENT
16
- #!/usr/bin/env bash
15
+ <<~CONTENT
16
+ #!/usr/bin/env bash
17
17
 
18
- current_branch=\`git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,'\`
19
- git_dir=\`git rev-parse --show-toplevel\`
20
- message=\`git log -1 --format=%B\`
21
- msync -m "\$message" #{arguments}
22
- CONTENT
18
+ current_branch=\`git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,'\`
19
+ git_dir=\`git rev-parse --show-toplevel\`
20
+ message=\`git log -1 --format=%B\`
21
+ msync -m "\$message" #{arguments}
22
+ CONTENT
23
23
  end
24
24
 
25
25
  def activate
@@ -28,9 +28,7 @@ CONTENT
28
28
  hook_args << "-b #{branch}" if branch
29
29
  hook_args << args if args
30
30
 
31
- File.open(hook_file, 'w') do |file|
32
- file.write(content(hook_args.join(' ')))
33
- end
31
+ File.write(hook_file, content(hook_args.join(' ')))
34
32
  end
35
33
 
36
34
  def deactivate
@@ -12,7 +12,7 @@ module ModuleSync
12
12
 
13
13
  def self.build(target_name)
14
14
  template_file = if !File.exist?("#{target_name}.erb") && File.exist?(target_name)
15
- STDERR.puts "Warning: using '#{target_name}' as template without '.erb' suffix"
15
+ $stderr.puts "Warning: using '#{target_name}' as template without '.erb' suffix"
16
16
  target_name
17
17
  else
18
18
  "#{target_name}.erb"
@@ -32,11 +32,8 @@ module ModuleSync
32
32
  end
33
33
 
34
34
  def self.sync(template, target_name)
35
- path = target_name.rpartition('/').first
36
- FileUtils.mkdir_p(path) unless path.empty?
37
- File.open(target_name, 'w') do |file|
38
- file.write(template)
39
- end
35
+ FileUtils.mkdir_p(File.dirname(target_name))
36
+ File.write(target_name, template)
40
37
  end
41
38
  end
42
39
  end