modulesync 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb584b8ba14d3c54ae9cf5ca91f24e8d1a087e73e45d98e45e79a29f6bbf2578
4
- data.tar.gz: c7fcf27f5bd05249da092a80a92b03430909a400b65c8be53671e2ae4a020459
3
+ metadata.gz: e8c67abecba12a55e8bf400dbdce4899d7ae3588ec7e297c4fe3ed46b7c9ce62
4
+ data.tar.gz: 0dfcfc208e51892329bab49da6dea0b33a04a5b90dd1769a898e9f82fd8d83eb
5
5
  SHA512:
6
- metadata.gz: b41beb1d73df97a4e006749788f0c6415f8d87c8e7d69cb07a7f74d202bfda4cf238558060d04044080c9b83b71633d182db506820634b92f86887329169b212
7
- data.tar.gz: 38f81a8fe37ff9b30f01f59299deb6de1d47ea03c89ea0d0d6df89b83b19f323b1ecaeab349ca7697bff37d9b9b4c7984c1f63083e22c2d721925a93c0d29559
6
+ metadata.gz: c113f5bb72bc8f77c0e07383f4ad13975915023ac865a68257eea3990fc888e193d5177d38003cf851d9ca1946ab13234c5642e75889f04cf9e52a3e3ef5f1fa
7
+ data.tar.gz: f12d610d26f07fc2ec75905a8d9637d88b063ff48f6367dbd04fdeab424052f551d28802300206b5f6ee73bda8ceeac9cf0f7bdb17b5d57f58203eaa6b6003d0
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: CI
3
+ on:
4
+ - pull_request
5
+ - push
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby:
14
+ - 2.5
15
+ - 2.6
16
+ - 2.7
17
+ - 3.0
18
+ env:
19
+ BUNDLE_WITHOUT: release
20
+ name: Ruby ${{ matrix.ruby }}
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - name: Install Ruby ${{ matrix.ruby }}
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+ - name: Run tests
29
+ run: bundle exec rake test
@@ -0,0 +1,30 @@
1
+ name: Release
2
+
3
+ on:
4
+ create:
5
+ ref_type: tag
6
+
7
+ jobs:
8
+ release:
9
+ runs-on: ubuntu-latest
10
+ if: github.repository == 'voxpupuli/modulesync'
11
+ env:
12
+ BUNDLE_WITHOUT: release
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Install Ruby 3.0
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: '3.0'
19
+ - name: Build gem
20
+ run: gem build *.gemspec
21
+ - name: Publish gem to rubygems.org
22
+ run: gem push *.gem
23
+ env:
24
+ GEM_HOST_API_KEY: '${{ secrets.RUBYGEMS_AUTH_TOKEN }}'
25
+ - name: Setup GitHub packages access
26
+ run: |
27
+ echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials
28
+ chmod 0600 /home/runner/.gem/credentials
29
+ - name: Publish gem to GitHub packages
30
+ run: gem push --key github --host https://rubygems.pkg.github.com/voxpupuli *.gem
data/.rubocop_todo.yml CHANGED
@@ -1,70 +1,51 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2015-10-07 12:25:07 -0700 using RuboCop version 0.34.2.
3
+ # on 2021-04-22 16:30:35 +0200 using RuboCop version 0.50.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 2
9
+ # Offense count: 1
10
10
  Lint/UselessAssignment:
11
11
  Exclude:
12
12
  - 'lib/modulesync.rb'
13
- - 'lib/modulesync/cli.rb'
14
13
 
15
- # Offense count: 6
14
+ # Offense count: 10
16
15
  Metrics/AbcSize:
17
- Max: 64
16
+ Max: 67
18
17
 
19
- # Offense count: 1
18
+ # Offense count: 2
20
19
  # Configuration parameters: CountComments.
21
20
  Metrics/ClassLength:
22
- Max: 107
21
+ Max: 128
23
22
 
24
- # Offense count: 4
23
+ # Offense count: 3
25
24
  Metrics/CyclomaticComplexity:
26
- Max: 13
25
+ Max: 12
27
26
 
28
- # Offense count: 8
27
+ # Offense count: 13
29
28
  # Configuration parameters: CountComments.
30
29
  Metrics/MethodLength:
31
- Max: 79
30
+ Max: 36
32
31
 
33
- # Offense count: 1
34
- # Configuration parameters: CountComments.
35
- Metrics/ModuleLength:
36
- Max: 140
37
-
38
- # Offense count: 4
32
+ # Offense count: 3
39
33
  Metrics/PerceivedComplexity:
40
- Max: 16
34
+ Max: 13
41
35
 
42
- # Offense count: 9
43
- # Configuration parameters: Exclude.
36
+ # Offense count: 8
44
37
  Style/Documentation:
45
38
  Exclude:
39
+ - 'spec/**/*'
40
+ - 'test/**/*'
46
41
  - 'lib/modulesync.rb'
47
42
  - 'lib/modulesync/cli.rb'
48
- - 'lib/modulesync/constants.rb'
49
- - 'lib/modulesync/git.rb'
50
43
  - 'lib/modulesync/hook.rb'
51
44
  - 'lib/modulesync/renderer.rb'
52
45
  - 'lib/modulesync/util.rb'
53
46
 
54
- # Offense count: 1
55
- Style/EachWithObject:
56
- Exclude:
57
- - 'lib/modulesync/util.rb'
58
-
59
- # Offense count: 1
60
- # Configuration parameters: MinBodyLength.
61
- Style/GuardClause:
62
- Exclude:
63
- - 'lib/modulesync/cli.rb'
64
-
65
47
  # Offense count: 1
66
48
  # Cop supports --auto-correct.
67
- # Configuration parameters: AllowAsExpressionSeparator.
68
- Style/Semicolon:
49
+ Style/EachWithObject:
69
50
  Exclude:
70
51
  - 'lib/modulesync/util.rb'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.1.0](https://github.com/voxpupuli/modulesync/tree/2.1.0) (2021-06-15)
6
+
7
+ [Full Changelog](https://github.com/voxpupuli/modulesync/compare/2.0.2...2.1.0)
8
+
9
+ **Merged pull requests:**
10
+
11
+ - publish to github packages + test on ruby 3 [\#222](https://github.com/voxpupuli/modulesync/pull/222) ([bastelfreak](https://github.com/bastelfreak))
12
+ - Rework exception handling [\#217](https://github.com/voxpupuli/modulesync/pull/217) ([neomilium](https://github.com/neomilium))
13
+ - Split generic and specific code [\#215](https://github.com/voxpupuli/modulesync/pull/215) ([neomilium](https://github.com/neomilium))
14
+ - Refactor repository related code [\#214](https://github.com/voxpupuli/modulesync/pull/214) ([neomilium](https://github.com/neomilium))
15
+ - Tests: Add tests for bump feature [\#213](https://github.com/voxpupuli/modulesync/pull/213) ([neomilium](https://github.com/neomilium))
16
+ - Refactor puppet modules properties [\#212](https://github.com/voxpupuli/modulesync/pull/212) ([neomilium](https://github.com/neomilium))
17
+ - Switch from Travis CI to GitHub Actions [\#205](https://github.com/voxpupuli/modulesync/pull/205) ([neomilium](https://github.com/neomilium))
18
+
5
19
  ## [2.0.2](https://github.com/voxpupuli/modulesync/tree/2.0.2) (2021-04-03)
6
20
 
7
21
  [Full Changelog](https://github.com/voxpupuli/modulesync/compare/2.0.1...2.0.2)
data/features/cli.feature CHANGED
@@ -35,6 +35,9 @@ Feature: CLI
35
35
  """
36
36
  And a git_base option appended to "modulesync.yml" for local tests
37
37
  And a directory named "moduleroot"
38
- When I run `msync update --noop --namespace fakenamespace`
38
+ When I run `msync update --noop --namespace fakenamespace --branch command-line-branch`
39
39
  Then the exit status should be 0
40
- And the output should match /Syncing fakenamespace/
40
+ And the output should contain:
41
+ """
42
+ Creating new branch command-line-branch
43
+ """
@@ -73,3 +73,13 @@ Given 'the puppet module {string} from {string} has the default branch named {st
73
73
  pmrr = ModuleSync::Faker::PuppetModuleRemoteRepo.new(name, namespace)
74
74
  pmrr.default_branch = default_branch
75
75
  end
76
+
77
+ Then('the puppet module {string} from {string} should have a tag named {string}') do |name, namespace, tag|
78
+ pmrr = ModuleSync::Faker::PuppetModuleRemoteRepo.new(name, namespace)
79
+ expect(pmrr.tags).to include(tag)
80
+ end
81
+
82
+ Then('the puppet module {string} from {string} should not have a tag named {string}') do |name, namespace, tag|
83
+ pmrr = ModuleSync::Faker::PuppetModuleRemoteRepo.new(name, namespace)
84
+ expect(pmrr.tags).not_to include(tag)
85
+ end
@@ -300,7 +300,7 @@ Feature: update
300
300
  """
301
301
  And the output should match:
302
302
  """
303
- Not managing Gemfile in puppet-test
303
+ Not managing 'Gemfile' in 'puppet-test'
304
304
  """
305
305
  And the exit status should be 0
306
306
  And the file named "modules/fakenamespace/puppet-test/Gemfile" should contain:
@@ -370,7 +370,7 @@ Feature: update
370
370
  When I run `msync update --offline`
371
371
  Then the output should contain:
372
372
  """
373
- Not managing spec/spec_helper.rb in puppet-apache
373
+ Not managing 'spec/spec_helper.rb' in 'puppet-apache'
374
374
  """
375
375
  And the exit status should be 0
376
376
  And the file named "modules/puppetlabs/puppet-apache/spec/spec_helper.rb" should contain:
@@ -461,6 +461,7 @@ Feature: update
461
461
  And a directory named "moduleroot"
462
462
  When I run `msync update --message "Running without changes"`
463
463
  Then the exit status should be 0
464
+ And the stdout should contain "There were no changes in 'modules/fakenamespace/puppet-test'. Not committing."
464
465
  And the puppet module "puppet-test" from "fakenamespace" should have no commits made by "Aruba"
465
466
 
466
467
  Scenario: When specifying configurations in managed_modules.yml
@@ -607,7 +608,7 @@ Feature: update
607
608
  Then the exit status should be 0
608
609
  And the output should match:
609
610
  """
610
- Not managing spec/spec_helper.rb in puppet-test
611
+ Not managing 'spec/spec_helper.rb' in 'puppet-test'
611
612
  """
612
613
  And the file named "modules/fakenamespace/puppet-test/global-test.md" should contain:
613
614
  """
@@ -0,0 +1,87 @@
1
+ Feature: Bump a new version after an update
2
+ Scenario: Bump the module version, update changelog and tag it after an update that produces changes
3
+ Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
4
+ And the puppet module "puppet-test" from "fakenamespace" has a file named "CHANGELOG.md" with:
5
+ """
6
+ ## 1965-04-14 - Release 0.4.2
7
+ """
8
+ And a file named "config_defaults.yml" with:
9
+ """
10
+ ---
11
+ new-file:
12
+ content: aruba
13
+ """
14
+ And a directory named "moduleroot"
15
+ And a file named "moduleroot/new-file.erb" with:
16
+ """
17
+ <%= @configs['content'] %>
18
+ """
19
+ When I run `msync update --message "Add new-file" --bump --changelog --tag`
20
+ Then the exit status should be 0
21
+ And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
22
+ And the stdout should contain:
23
+ """
24
+ Bumped to version 0.4.3
25
+ """
26
+ And the stdout should contain:
27
+ """
28
+ Tagging with 0.4.3
29
+ """
30
+ And the file named "modules/fakenamespace/puppet-test/CHANGELOG.md" should contain "0.4.3"
31
+ And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
32
+ And the puppet module "puppet-test" from "fakenamespace" should have a tag named "0.4.3"
33
+
34
+ Scenario: Bump the module version after an update that produces changes
35
+ Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
36
+ And a file named "config_defaults.yml" with:
37
+ """
38
+ ---
39
+ new-file:
40
+ content: aruba
41
+ """
42
+ And a directory named "moduleroot"
43
+ And a file named "moduleroot/new-file.erb" with:
44
+ """
45
+ <%= @configs['content'] %>
46
+ """
47
+ When I run `msync update --message "Add new-file" --bump`
48
+ Then the exit status should be 0
49
+ And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
50
+ And the stdout should contain:
51
+ """
52
+ Bumped to version 0.4.3
53
+ """
54
+ And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
55
+ And the puppet module "puppet-test" from "fakenamespace" should not have a tag named "0.4.3"
56
+
57
+ Scenario: Bump the module version with changelog update when no CHANGELOG.md is available
58
+ Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
59
+ And a file named "config_defaults.yml" with:
60
+ """
61
+ ---
62
+ new-file:
63
+ content: aruba
64
+ """
65
+ And a directory named "moduleroot"
66
+ And a file named "moduleroot/new-file.erb" with:
67
+ """
68
+ <%= @configs['content'] %>
69
+ """
70
+ When I run `msync update --message "Add new-file" --bump --changelog`
71
+ Then the exit status should be 0
72
+ And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
73
+ And the stdout should contain:
74
+ """
75
+ Bumped to version 0.4.3
76
+ No CHANGELOG.md file found, not updating.
77
+ """
78
+ And the file named "modules/fakenamespace/puppet-test/CHANGELOG.md" should not exist
79
+ And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
80
+
81
+ Scenario: Dont bump the module version after an update that produces no changes
82
+ Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
83
+ And a directory named "moduleroot"
84
+ When I run `msync update --message "Add new-file" --bump --tag`
85
+ Then the exit status should be 0
86
+ And the puppet module "puppet-test" from "fakenamespace" should have no commits made by "Aruba"
87
+ And the puppet module "puppet-test" from "fakenamespace" should not have a tag named "0.4.3"
data/lib/modulesync.rb CHANGED
@@ -1,15 +1,19 @@
1
1
  require 'fileutils'
2
2
  require 'pathname'
3
+
3
4
  require 'modulesync/cli'
4
5
  require 'modulesync/constants'
5
- require 'modulesync/git'
6
6
  require 'modulesync/hook'
7
+ require 'modulesync/puppet_module'
7
8
  require 'modulesync/renderer'
8
9
  require 'modulesync/settings'
9
10
  require 'modulesync/util'
11
+
10
12
  require 'monkey_patches'
11
13
 
12
14
  module ModuleSync # rubocop:disable Metrics/ModuleLength
15
+ class Error < StandardError; end
16
+
13
17
  include Constants
14
18
 
15
19
  def self.config_defaults
@@ -21,12 +25,12 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
21
25
  }
22
26
  end
23
27
 
24
- def self.local_file(config_path, file)
25
- File.join(config_path, MODULE_FILES_DIR, file)
28
+ def self.options
29
+ @options
26
30
  end
27
31
 
28
- def self.module_file(project_root, namespace, puppet_module, *parts)
29
- File.join(project_root, namespace, puppet_module, *parts)
32
+ def self.local_file(config_path, file)
33
+ File.join(config_path, MODULE_FILES_DIR, file)
30
34
  end
31
35
 
32
36
  # List all template files.
@@ -50,7 +54,11 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
50
54
  file_list.map { |file| file.sub(/#{path}/, '') }
51
55
  end
52
56
 
53
- def self.managed_modules(config_file, filter, negative_filter)
57
+ def self.managed_modules
58
+ config_file = config_path(options[:managed_modules_conf], options)
59
+ filter = options[:filter]
60
+ negative_filter = options[:negative_filter]
61
+
54
62
  managed_modules = Util.parse_config(config_file)
55
63
  if managed_modules.empty?
56
64
  $stderr.puts "No modules found in #{config_file}." \
@@ -59,12 +67,7 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
59
67
  end
60
68
  managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
61
69
  managed_modules.reject! { |m| m =~ Regexp.new(negative_filter) } unless negative_filter.nil?
62
- managed_modules
63
- end
64
-
65
- def self.module_name(module_name, default_namespace)
66
- return [default_namespace, module_name] unless module_name.include?('/')
67
- ns, mod = module_name.split('/')
70
+ managed_modules.map { |given_name, options| PuppetModule.new(given_name, options) }
68
71
  end
69
72
 
70
73
  def self.hook(options)
@@ -78,11 +81,11 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
78
81
  end
79
82
  end
80
83
 
81
- def self.manage_file(filename, settings, options)
84
+ def self.manage_file(puppet_module, filename, settings, options)
82
85
  namespace = settings.additional_settings[:namespace]
83
86
  module_name = settings.additional_settings[:puppet_module]
84
87
  configs = settings.build_file_configs(filename)
85
- target_file = module_file(options[:project_root], namespace, module_name, filename)
88
+ target_file = puppet_module.path(filename)
86
89
  if configs['delete']
87
90
  Renderer.remove(target_file)
88
91
  else
@@ -92,50 +95,52 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
92
95
  # Meta data passed to the template as @metadata[:name]
93
96
  metadata = {
94
97
  :module_name => module_name,
95
- :workdir => module_file(options[:project_root], namespace, module_name),
98
+ :workdir => puppet_module.working_directory,
96
99
  :target_file => target_file,
97
100
  }
98
101
  template = Renderer.render(erb, configs, metadata)
99
102
  Renderer.sync(template, target_file)
100
- rescue # rubocop:disable Lint/RescueWithoutErrorClass
101
- $stderr.puts "Error while rendering #{filename}"
103
+ rescue StandardError => e
104
+ $stderr.puts "#{puppet_module.given_name}: Error while rendering file: '#{filename}'"
102
105
  raise
103
106
  end
104
107
  end
105
108
  end
106
109
 
107
- def self.manage_module(puppet_module, module_files, module_options, defaults, options)
108
- default_namespace = options[:namespace]
109
- if module_options.is_a?(Hash) && module_options.key?(:namespace)
110
- default_namespace = module_options[:namespace]
111
- end
112
- namespace, module_name = module_name(puppet_module, default_namespace)
113
- git_repo = File.join(namespace, module_name)
114
- unless options[:offline]
115
- Git.pull(options[:git_base], git_repo, options[:branch], options[:project_root], module_options || {})
116
- end
110
+ def self.manage_module(puppet_module, module_files, defaults)
111
+ puts "Syncing '#{puppet_module.given_name}'"
112
+ puppet_module.repository.prepare_workspace(options[:branch]) unless options[:offline]
117
113
 
118
- module_configs = Util.parse_config(module_file(options[:project_root], namespace, module_name, MODULE_CONF_FILE))
114
+ module_configs = Util.parse_config puppet_module.path(MODULE_CONF_FILE)
119
115
  settings = Settings.new(defaults[GLOBAL_DEFAULTS_KEY] || {},
120
116
  defaults,
121
117
  module_configs[GLOBAL_DEFAULTS_KEY] || {},
122
118
  module_configs,
123
- :puppet_module => module_name,
119
+ :puppet_module => puppet_module.repository_name,
124
120
  :git_base => options[:git_base],
125
- :namespace => namespace)
121
+ :namespace => puppet_module.repository_namespace)
122
+
126
123
  settings.unmanaged_files(module_files).each do |filename|
127
- $stdout.puts "Not managing #{filename} in #{module_name}"
124
+ $stdout.puts "Not managing '#{filename}' in '#{puppet_module.given_name}'"
128
125
  end
129
126
 
130
127
  files_to_manage = settings.managed_files(module_files)
131
- files_to_manage.each { |filename| manage_file(filename, settings, options) }
128
+ files_to_manage.each { |filename| manage_file(puppet_module, filename, settings, options) }
132
129
 
133
130
  if options[:noop]
134
- Git.update_noop(git_repo, options)
135
- options[:pr] && pr(module_options).manage(namespace, module_name, options)
131
+ puts "Using no-op. Files in '#{puppet_module.given_name}' may be changed but will not be committed."
132
+ puppet_module.repository.show_changes(options)
133
+ options[:pr] && \
134
+ pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
136
135
  elsif !options[:offline]
137
- pushed = Git.update(git_repo, files_to_manage, options)
138
- pushed && options[:pr] && pr(module_options).manage(namespace, module_name, options)
136
+ pushed = puppet_module.repository.submit_changes(files_to_manage, options)
137
+ # Only bump/tag if pushing didn't fail (i.e. there were changes)
138
+ if pushed && options[:bump]
139
+ new = puppet_module.bump(options[:message], options[:changelog])
140
+ puppet_module.repository.tag(new, options[:tag_pattern]) if options[:tag]
141
+ end
142
+ pushed && options[:pr] && \
143
+ pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
139
144
  end
140
145
  end
141
146
 
@@ -148,9 +153,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
148
153
  self.class.config_path(file, options)
149
154
  end
150
155
 
151
- def self.update(options)
152
- options = config_defaults.merge(options)
156
+ def self.update(cli_options)
157
+ @options = config_defaults.merge(cli_options)
153
158
  defaults = Util.parse_config(config_path(CONF_FILE, options))
159
+
154
160
  if options[:pr]
155
161
  unless options[:branch]
156
162
  $stderr.puts 'A branch must be specified with --branch to use --pr!'
@@ -164,28 +170,28 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
164
170
  local_files = find_template_files(local_template_dir)
165
171
  module_files = relative_names(local_files, local_template_dir)
166
172
 
167
- managed_modules = self.managed_modules(config_path(options[:managed_modules_conf], options),
168
- options[:filter],
169
- options[:negative_filter])
170
-
171
173
  errors = false
172
174
  # managed_modules is either an array or a hash
173
- managed_modules.each do |puppet_module, module_options|
175
+ managed_modules.each do |puppet_module|
174
176
  begin
175
- mod_options = module_options.nil? ? nil : Util.symbolize_keys(module_options)
176
- manage_module(puppet_module, module_files, mod_options, defaults, options)
177
- rescue # rubocop:disable Lint/RescueWithoutErrorClass
178
- $stderr.puts "Error while updating #{puppet_module}"
177
+ manage_module(puppet_module, module_files, defaults)
178
+ rescue ModuleSync::Error, Git::GitExecuteError => e
179
+ message = e.message || "Error during '#{options[:command]}'"
180
+ $stderr.puts "#{puppet_module.given_name}: #{message}"
181
+ exit 1 unless options[:skip_broken]
182
+ errors = true
183
+ $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
184
+ rescue StandardError => e
179
185
  raise unless options[:skip_broken]
180
186
  errors = true
181
- $stdout.puts "Skipping #{puppet_module} as update process failed"
187
+ $stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
182
188
  end
183
189
  end
184
190
  exit 1 if errors && options[:fail_on_warnings]
185
191
  end
186
192
 
187
- def self.pr(module_options)
188
- module_options ||= {}
193
+ def self.pr(puppet_module)
194
+ module_options = puppet_module.options
189
195
  github_conf = module_options[:github]
190
196
  gitlab_conf = module_options[:gitlab]
191
197
 
@@ -0,0 +1,37 @@
1
+ require 'puppet_blacksmith'
2
+
3
+ require 'modulesync/source_code'
4
+
5
+ module ModuleSync
6
+ # Provide methods to manipulate puppet module code
7
+ class PuppetModule < SourceCode
8
+ def update_changelog(version, message)
9
+ changelog = path('CHANGELOG.md')
10
+ if File.exist?(changelog)
11
+ puts "Updating #{changelog} for version #{version}"
12
+ changes = File.readlines(changelog)
13
+ File.open(changelog, 'w') do |f|
14
+ date = Time.now.strftime('%Y-%m-%d')
15
+ f.puts "## #{date} - Release #{version}\n\n"
16
+ f.puts "#{message}\n\n"
17
+ # Add old lines again
18
+ f.puts changes
19
+ end
20
+ repository.git.add('CHANGELOG.md')
21
+ else
22
+ puts 'No CHANGELOG.md file found, not updating.'
23
+ end
24
+ end
25
+
26
+ def bump(message, changelog = false)
27
+ m = Blacksmith::Modulefile.new path('metadata.json')
28
+ new = m.bump!
29
+ puts "Bumped to version #{new}"
30
+ repository.git.add('metadata.json')
31
+ update_changelog(new, message) if changelog
32
+ repository.git.commit("Release version #{new}")
33
+ repository.git.push
34
+ new
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,158 @@
1
+ require 'git'
2
+
3
+ module ModuleSync
4
+ # Wrapper for Git in ModuleSync context
5
+ class Repository
6
+ def initialize(directory:, remote:)
7
+ @directory = directory
8
+ @remote = remote
9
+ end
10
+
11
+ def git
12
+ @git ||= Git.open @directory
13
+ end
14
+
15
+ # This is an alias to minimize code alteration
16
+ def repo
17
+ git
18
+ end
19
+
20
+ def remote_branch_exists?(branch)
21
+ repo.branches.remote.collect(&:name).include?(branch)
22
+ end
23
+
24
+ def local_branch_exists?(branch)
25
+ repo.branches.local.collect(&:name).include?(branch)
26
+ end
27
+
28
+ def remote_branch_differ?(local_branch, remote_branch)
29
+ !remote_branch_exists?(remote_branch) ||
30
+ repo.diff("#{local_branch}..origin/#{remote_branch}").any?
31
+ end
32
+
33
+ def default_branch
34
+ symbolic_ref = repo.branches.find { |b| b.full =~ %r{remotes/origin/HEAD} }
35
+ return unless symbolic_ref
36
+ %r{remotes/origin/HEAD\s+->\s+origin/(?<branch>.+?)$}.match(symbolic_ref.full)[:branch]
37
+ end
38
+
39
+ def switch_branch(branch)
40
+ unless branch
41
+ branch = default_branch
42
+ puts "Using repository's default branch: #{branch}"
43
+ end
44
+ return if repo.current_branch == branch
45
+
46
+ if local_branch_exists?(branch)
47
+ puts "Switching to branch #{branch}"
48
+ repo.checkout(branch)
49
+ elsif remote_branch_exists?(branch)
50
+ puts "Creating local branch #{branch} from origin/#{branch}"
51
+ repo.checkout("origin/#{branch}")
52
+ repo.branch(branch).checkout
53
+ else
54
+ repo.checkout('origin/master')
55
+ puts "Creating new branch #{branch}"
56
+ repo.branch(branch).checkout
57
+ end
58
+ end
59
+
60
+ def prepare_workspace(branch)
61
+ # Repo needs to be cloned in the cwd
62
+ if !Dir.exist?("#{@directory}/.git")
63
+ puts 'Cloning repository fresh'
64
+ puts "Cloning from '#{@remote}'"
65
+ @git = Git.clone(@remote, @directory)
66
+ switch_branch(branch)
67
+ # Repo already cloned, check out master and override local changes
68
+ else
69
+ # Some versions of git can't properly handle managing a repo from outside the repo directory
70
+ Dir.chdir(@directory) do
71
+ puts "Overriding any local changes to repository in '#{@directory}'"
72
+ @git = Git.open('.')
73
+ repo.fetch
74
+ repo.reset_hard
75
+ switch_branch(branch)
76
+ git.pull('origin', branch) if remote_branch_exists?(branch)
77
+ end
78
+ end
79
+ end
80
+
81
+ def tag(version, tag_pattern)
82
+ tag = tag_pattern % version
83
+ puts "Tagging with #{tag}"
84
+ repo.add_tag(tag)
85
+ repo.push('origin', tag)
86
+ end
87
+
88
+ def checkout_branch(branch)
89
+ selected_branch = branch || repo.current_branch || 'master'
90
+ repo.branch(selected_branch).checkout
91
+ selected_branch
92
+ end
93
+
94
+ # Git add/rm, git commit, git push
95
+ def submit_changes(files, options)
96
+ message = options[:message]
97
+ branch = checkout_branch(options[:branch])
98
+ files.each do |file|
99
+ if repo.status.deleted.include?(file)
100
+ repo.remove(file)
101
+ elsif File.exist?("#{@directory}/#{file}")
102
+ repo.add(file)
103
+ end
104
+ end
105
+ begin
106
+ opts_commit = {}
107
+ opts_push = {}
108
+ opts_commit = { :amend => true } if options[:amend]
109
+ opts_push = { :force => true } if options[:force]
110
+ if options[:pre_commit_script]
111
+ script = "#{File.dirname(File.dirname(__FILE__))}/../contrib/#{options[:pre_commit_script]}"
112
+ `#{script} #{@directory}`
113
+ end
114
+ repo.commit(message, opts_commit)
115
+ if options[:remote_branch]
116
+ if remote_branch_differ?(branch, options[:remote_branch])
117
+ repo.push('origin', "#{branch}:#{options[:remote_branch]}", opts_push)
118
+ end
119
+ else
120
+ repo.push('origin', branch, opts_push)
121
+ end
122
+ rescue Git::GitExecuteError => e
123
+ raise unless e.message.match?(/working (directory|tree) clean/)
124
+
125
+ puts "There were no changes in '#{@directory}'. Not committing."
126
+ return false
127
+ end
128
+
129
+ true
130
+ end
131
+
132
+ # Needed because of a bug in the git gem that lists ignored files as
133
+ # untracked under some circumstances
134
+ # https://github.com/schacon/ruby-git/issues/130
135
+ def untracked_unignored_files
136
+ ignore_path = "#{@directory}/.gitignore"
137
+ ignored = File.exist?(ignore_path) ? File.read(ignore_path).split : []
138
+ repo.status.untracked.keep_if { |f, _| ignored.none? { |i| File.fnmatch(i, f) } }
139
+ end
140
+
141
+ def show_changes(options)
142
+ checkout_branch(options[:branch])
143
+
144
+ puts 'Files changed:'
145
+ repo.diff('HEAD', '--').each do |diff|
146
+ puts diff.patch
147
+ end
148
+
149
+ puts 'Files added:'
150
+ untracked_unignored_files.each_key do |file|
151
+ puts file
152
+ end
153
+
154
+ puts "\n\n"
155
+ puts '--------------------------------'
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,57 @@
1
+ require 'modulesync'
2
+ require 'modulesync/repository'
3
+ require 'modulesync/util'
4
+
5
+ module ModuleSync
6
+ # Provide methods to retrieve source code attributes
7
+ class SourceCode
8
+ attr_reader :given_name
9
+ attr_reader :options
10
+
11
+ def initialize(given_name, options)
12
+ @options = Util.symbolize_keys(options || {})
13
+
14
+ @given_name = given_name
15
+
16
+ return unless given_name.include?('/')
17
+
18
+ @repository_name = given_name.split('/').last
19
+ @repository_namespace = given_name.split('/')[0...-1].join('/')
20
+ end
21
+
22
+ def repository
23
+ @repository ||= Repository.new directory: working_directory, remote: repository_remote
24
+ end
25
+
26
+ def repository_name
27
+ @repository_name ||= given_name
28
+ end
29
+
30
+ def repository_namespace
31
+ @repository_namespace ||= @options[:namespace] || ModuleSync.options[:namespace]
32
+ end
33
+
34
+ def repository_path
35
+ @repository_path ||= "#{repository_namespace}/#{repository_name}"
36
+ end
37
+
38
+ def repository_remote
39
+ @repository_remote ||= @options[:remote] || _repository_remote
40
+ end
41
+
42
+ def working_directory
43
+ @working_directory ||= File.join(ModuleSync.options[:project_root], repository_path)
44
+ end
45
+
46
+ def path(*parts)
47
+ File.join(working_directory, *parts)
48
+ end
49
+
50
+ private
51
+
52
+ def _repository_remote
53
+ git_base = ModuleSync.options[:git_base]
54
+ git_base.start_with?('file://') ? "#{git_base}#{repository_path}" : "#{git_base}#{repository_path}.git"
55
+ end
56
+ end
57
+ 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.2'
6
+ spec.version = '2.1.0'
7
7
  spec.authors = ['Vox Pupuli']
8
8
  spec.email = ['voxpupuli@groups.io']
9
9
  spec.summary = 'Puppet Module Synchronizer'
@@ -99,6 +99,12 @@ module ModuleSync
99
99
  end
100
100
  end
101
101
 
102
+ def tags
103
+ FileUtils.chdir(bare_repo_dir) do
104
+ return run %w{git tag --list}
105
+ end
106
+ end
107
+
102
108
  def remote_url
103
109
  "file://#{bare_repo_dir}"
104
110
  end
@@ -5,7 +5,7 @@ describe ModuleSync do
5
5
  it 'loads the managed modules from the specified :managed_modules_conf' do
6
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
- expect(ModuleSync).to receive(:managed_modules).with('./test_file.yml', nil, nil).and_return([])
8
+ expect(ModuleSync).to receive(:managed_modules).with(no_args).and_return([])
9
9
 
10
10
  options = { managed_modules_conf: 'test_file.yml' }
11
11
  ModuleSync.update(options)
@@ -14,8 +14,12 @@ describe ModuleSync do
14
14
 
15
15
  context '::pr' do
16
16
  describe "Raise Error" do
17
+ let(:puppet_module) do
18
+ ModuleSync::PuppetModule.new 'puppet-test', remote: 'dummy'
19
+ end
20
+
17
21
  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
22
+ expect { ModuleSync.pr(puppet_module) }.to raise_error(RuntimeError).and output(/No GitHub or GitLab token specified for --pr/).to_stderr
19
23
  end
20
24
  end
21
25
  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.2
4
+ version: 2.1.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: 2021-04-03 00:00:00.000000000 Z
11
+ date: 2021-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
@@ -165,11 +165,12 @@ extensions: []
165
165
  extra_rdoc_files: []
166
166
  files:
167
167
  - ".config/cucumber.yml"
168
+ - ".github/workflows/ci.yml"
169
+ - ".github/workflows/release.yml"
168
170
  - ".gitignore"
169
171
  - ".rspec"
170
172
  - ".rubocop.yml"
171
173
  - ".rubocop_todo.yml"
172
- - ".travis.yml"
173
174
  - CHANGELOG.md
174
175
  - Gemfile
175
176
  - HISTORY.md
@@ -184,16 +185,19 @@ files:
184
185
  - features/support/env.rb
185
186
  - features/update.feature
186
187
  - features/update/bad_context.feature
188
+ - features/update/bump_version.feature
187
189
  - lib/modulesync.rb
188
190
  - lib/modulesync/cli.rb
189
191
  - lib/modulesync/cli/thor.rb
190
192
  - lib/modulesync/constants.rb
191
- - lib/modulesync/git.rb
192
193
  - lib/modulesync/hook.rb
193
194
  - lib/modulesync/pr/github.rb
194
195
  - lib/modulesync/pr/gitlab.rb
196
+ - lib/modulesync/puppet_module.rb
195
197
  - lib/modulesync/renderer.rb
198
+ - lib/modulesync/repository.rb
196
199
  - lib/modulesync/settings.rb
200
+ - lib/modulesync/source_code.rb
197
201
  - lib/modulesync/util.rb
198
202
  - lib/monkey_patches.rb
199
203
  - modulesync.gemspec
@@ -223,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
227
  - !ruby/object:Gem::Version
224
228
  version: '0'
225
229
  requirements: []
226
- rubygems_version: 3.1.2
230
+ rubygems_version: 3.2.15
227
231
  signing_key:
228
232
  specification_version: 4
229
233
  summary: Puppet Module Synchronizer
@@ -234,6 +238,7 @@ test_files:
234
238
  - features/support/env.rb
235
239
  - features/update.feature
236
240
  - features/update/bad_context.feature
241
+ - features/update/bump_version.feature
237
242
  - spec/helpers/faker.rb
238
243
  - spec/helpers/faker/puppet_module_remote_repo.rb
239
244
  - spec/spec_helper.rb
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- dist: focal
6
- script: 'bundle exec rake test'
7
- bundler_args: --without release
8
- rvm:
9
- - 2.5
10
- - 2.6
11
- - 2.7
12
- notifications:
13
- email: false
14
- irc:
15
- on_success: always
16
- on_failure: always
17
- channels:
18
- - "chat.freenode.org#voxpupuli-notifications"
19
- deploy:
20
- provider: rubygems
21
- api_key:
22
- secure: "Tbf1EbLEobIIox+fftJZADZsfQQ6kl0urcMNetK7NJzFo/negD/WyJIUj3kro/B7buyYADEjTui/JR4o8EPbugfM3ie5vYOd5k3AesSzbdr4BSwGe/cGbGOB7/PZuGfFLkb94/FiCU2mIwibkbh1rHWGlBoPj7ntL0+5ZtdvsM4="
23
- gem: modulesync
24
- on:
25
- rvm: 2.7
26
- tags: true
27
- all_branches: true
28
- repo: voxpupuli/modulesync
@@ -1,194 +0,0 @@
1
- require 'git'
2
- require 'puppet_blacksmith'
3
-
4
- module ModuleSync
5
- module Git # rubocop:disable Metrics/ModuleLength
6
- include Constants
7
-
8
- def self.remote_branch_exists?(repo, branch)
9
- repo.branches.remote.collect(&:name).include?(branch)
10
- end
11
-
12
- def self.local_branch_exists?(repo, branch)
13
- repo.branches.local.collect(&:name).include?(branch)
14
- end
15
-
16
- def self.remote_branch_differ?(repo, local_branch, remote_branch)
17
- !remote_branch_exists?(repo, remote_branch) ||
18
- repo.diff("#{local_branch}..origin/#{remote_branch}").any?
19
- end
20
-
21
- def self.default_branch(repo)
22
- symbolic_ref = repo.branches.find { |b| b.full =~ %r{remotes/origin/HEAD} }
23
- return unless symbolic_ref
24
- %r{remotes/origin/HEAD\s+->\s+origin/(?<branch>.+?)$}.match(symbolic_ref.full)[:branch]
25
- end
26
-
27
- def self.switch_branch(repo, branch)
28
- unless branch
29
- branch = default_branch(repo)
30
- puts "Using repository's default branch: #{branch}"
31
- end
32
- return if repo.current_branch == branch
33
-
34
- if local_branch_exists?(repo, branch)
35
- puts "Switching to branch #{branch}"
36
- repo.checkout(branch)
37
- elsif remote_branch_exists?(repo, branch)
38
- puts "Creating local branch #{branch} from origin/#{branch}"
39
- repo.checkout("origin/#{branch}")
40
- repo.branch(branch).checkout
41
- else
42
- repo.checkout('origin/master')
43
- puts "Creating new branch #{branch}"
44
- repo.branch(branch).checkout
45
- end
46
- end
47
-
48
- def self.pull(git_base, name, branch, project_root, opts)
49
- puts "Syncing #{name}"
50
- Dir.mkdir(project_root) unless Dir.exist?(project_root)
51
-
52
- # Repo needs to be cloned in the cwd
53
- if !Dir.exist?("#{project_root}/#{name}") || !Dir.exist?("#{project_root}/#{name}/.git")
54
- puts 'Cloning repository fresh'
55
- remote = opts[:remote] || (git_base.start_with?('file://') ? "#{git_base}#{name}" : "#{git_base}#{name}.git")
56
- local = "#{project_root}/#{name}"
57
- puts "Cloning from #{remote}"
58
- repo = ::Git.clone(remote, local)
59
- switch_branch(repo, branch)
60
- # Repo already cloned, check out master and override local changes
61
- else
62
- # Some versions of git can't properly handle managing a repo from outside the repo directory
63
- Dir.chdir("#{project_root}/#{name}") do
64
- puts "Overriding any local changes to repositories in #{project_root}"
65
- repo = ::Git.open('.')
66
- repo.fetch
67
- repo.reset_hard
68
- switch_branch(repo, branch)
69
- repo.pull('origin', branch) if remote_branch_exists?(repo, branch)
70
- end
71
- end
72
- end
73
-
74
- def self.update_changelog(repo, version, message, module_root)
75
- changelog = "#{module_root}/CHANGELOG.md"
76
- if File.exist?(changelog)
77
- puts "Updating #{changelog} for version #{version}"
78
- changes = File.readlines(changelog)
79
- File.open(changelog, 'w') do |f|
80
- date = Time.now.strftime('%Y-%m-%d')
81
- f.puts "## #{date} - Release #{version}\n\n"
82
- f.puts "#{message}\n\n"
83
- # Add old lines again
84
- f.puts changes
85
- end
86
- repo.add('CHANGELOG.md')
87
- else
88
- puts 'No CHANGELOG.md file found, not updating.'
89
- end
90
- end
91
-
92
- def self.bump(repo, m, message, module_root, changelog = false)
93
- new = m.bump!
94
- puts "Bumped to version #{new}"
95
- repo.add('metadata.json')
96
- update_changelog(repo, new, message, module_root) if changelog
97
- repo.commit("Release version #{new}")
98
- repo.push
99
- new
100
- end
101
-
102
- def self.tag(repo, version, tag_pattern)
103
- tag = tag_pattern % version
104
- puts "Tagging with #{tag}"
105
- repo.add_tag(tag)
106
- repo.push('origin', tag)
107
- end
108
-
109
- def self.checkout_branch(repo, branch)
110
- selected_branch = branch || repo.current_branch || 'master'
111
- repo.branch(selected_branch).checkout
112
- selected_branch
113
- end
114
- private_class_method :checkout_branch
115
-
116
- # Git add/rm, git commit, git push
117
- def self.update(name, files, options)
118
- module_root = "#{options[:project_root]}/#{name}"
119
- message = options[:message]
120
- repo = ::Git.open(module_root)
121
- branch = checkout_branch(repo, options[:branch])
122
- files.each do |file|
123
- if repo.status.deleted.include?(file)
124
- repo.remove(file)
125
- elsif File.exist?("#{module_root}/#{file}")
126
- repo.add(file)
127
- end
128
- end
129
- begin
130
- opts_commit = {}
131
- opts_push = {}
132
- opts_commit = { :amend => true } if options[:amend]
133
- opts_push = { :force => true } if options[:force]
134
- if options[:pre_commit_script]
135
- script = "#{File.dirname(File.dirname(__FILE__))}/../contrib/#{options[:pre_commit_script]}"
136
- `#{script} #{module_root}`
137
- end
138
- repo.commit(message, opts_commit)
139
- if options[:remote_branch]
140
- if remote_branch_differ?(repo, branch, options[:remote_branch])
141
- repo.push('origin', "#{branch}:#{options[:remote_branch]}", opts_push)
142
- end
143
- else
144
- repo.push('origin', branch, opts_push)
145
- end
146
- # Only bump/tag if pushing didn't fail (i.e. there were changes)
147
- m = Blacksmith::Modulefile.new("#{module_root}/metadata.json")
148
- if options[:bump]
149
- new = bump(repo, m, message, module_root, options[:changelog])
150
- tag(repo, new, options[:tag_pattern]) if options[:tag]
151
- end
152
- rescue ::Git::GitExecuteError => git_error
153
- if git_error.message.match?(/working (directory|tree) clean/)
154
- puts "There were no files to update in #{name}. Not committing."
155
- return false
156
- else
157
- puts git_error
158
- raise
159
- end
160
- end
161
-
162
- true
163
- end
164
-
165
- # Needed because of a bug in the git gem that lists ignored files as
166
- # untracked under some circumstances
167
- # https://github.com/schacon/ruby-git/issues/130
168
- def self.untracked_unignored_files(repo)
169
- ignore_path = "#{repo.dir.path}/.gitignore"
170
- ignored = File.exist?(ignore_path) ? File.read(ignore_path).split : []
171
- repo.status.untracked.keep_if { |f, _| ignored.none? { |i| File.fnmatch(i, f) } }
172
- end
173
-
174
- def self.update_noop(name, options)
175
- puts "Using no-op. Files in #{name} may be changed but will not be committed."
176
-
177
- repo = ::Git.open("#{options[:project_root]}/#{name}")
178
- checkout_branch(repo, options[:branch])
179
-
180
- puts 'Files changed:'
181
- repo.diff('HEAD', '--').each do |diff|
182
- puts diff.patch
183
- end
184
-
185
- puts 'Files added:'
186
- untracked_unignored_files(repo).each_key do |file|
187
- puts file
188
- end
189
-
190
- puts "\n\n"
191
- puts '--------------------------------'
192
- end
193
- end
194
- end