modulesync 2.0.2 → 2.1.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.
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