r10k 3.9.1 → 3.11.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +1 -1
  3. data/.travis.yml +0 -10
  4. data/CHANGELOG.mkd +28 -0
  5. data/README.mkd +6 -0
  6. data/doc/dynamic-environments/configuration.mkd +21 -0
  7. data/doc/puppetfile.mkd +15 -1
  8. data/integration/Rakefile +3 -1
  9. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  10. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +21 -25
  11. data/integration/tests/user_scenario/complex_workflow/multi_env_add_change_remove.rb +3 -3
  12. data/integration/tests/user_scenario/complex_workflow/multi_env_remove_re-add.rb +3 -3
  13. data/integration/tests/user_scenario/complex_workflow/multi_env_unamanaged.rb +3 -3
  14. data/lib/r10k/action/base.rb +6 -3
  15. data/lib/r10k/action/deploy/display.rb +6 -3
  16. data/lib/r10k/action/deploy/environment.rb +15 -4
  17. data/lib/r10k/action/deploy/module.rb +37 -8
  18. data/lib/r10k/action/runner.rb +45 -10
  19. data/lib/r10k/cli/deploy.rb +4 -0
  20. data/lib/r10k/git.rb +3 -0
  21. data/lib/r10k/git/cache.rb +1 -1
  22. data/lib/r10k/git/rugged/credentials.rb +77 -0
  23. data/lib/r10k/git/stateful_repository.rb +1 -0
  24. data/lib/r10k/initializers.rb +10 -0
  25. data/lib/r10k/module/base.rb +37 -0
  26. data/lib/r10k/module/forge.rb +7 -2
  27. data/lib/r10k/module/git.rb +2 -1
  28. data/lib/r10k/module/svn.rb +2 -1
  29. data/lib/r10k/module_loader/puppetfile.rb +206 -0
  30. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  31. data/lib/r10k/puppetfile.rb +83 -160
  32. data/lib/r10k/settings.rb +47 -2
  33. data/lib/r10k/settings/definition.rb +1 -1
  34. data/lib/r10k/source/base.rb +10 -0
  35. data/lib/r10k/source/git.rb +5 -0
  36. data/lib/r10k/source/svn.rb +4 -0
  37. data/lib/r10k/util/purgeable.rb +70 -8
  38. data/lib/r10k/version.rb +1 -1
  39. data/locales/r10k.pot +129 -57
  40. data/r10k.gemspec +2 -0
  41. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  42. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  43. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  44. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  45. data/spec/unit/action/deploy/environment_spec.rb +25 -0
  46. data/spec/unit/action/deploy/module_spec.rb +216 -14
  47. data/spec/unit/action/runner_spec.rb +129 -25
  48. data/spec/unit/git/cache_spec.rb +14 -0
  49. data/spec/unit/git/rugged/credentials_spec.rb +29 -0
  50. data/spec/unit/git/stateful_repository_spec.rb +5 -0
  51. data/spec/unit/module/base_spec.rb +46 -0
  52. data/spec/unit/module/forge_spec.rb +27 -1
  53. data/spec/unit/module/git_spec.rb +17 -8
  54. data/spec/unit/module/svn_spec.rb +18 -0
  55. data/spec/unit/module_loader/puppetfile_spec.rb +343 -0
  56. data/spec/unit/module_spec.rb +28 -0
  57. data/spec/unit/puppetfile_spec.rb +127 -191
  58. data/spec/unit/settings_spec.rb +24 -2
  59. data/spec/unit/util/purgeable_spec.rb +38 -6
  60. metadata +23 -2
@@ -18,14 +18,18 @@ module R10K
18
18
  # @param opts [Hash] A hash of options defined in #allowed_initialized_opts
19
19
  # and managed by the SetOps mixin within the Action::Base class.
20
20
  # Corresponds to the CLI flags and options.
21
- # @param argv [CRI::ArgumentList] A list-like collection of the remaining
22
- # arguments to the CLI invocation (after removing flags and options).
21
+ # @param argv [Enumerable] Typically CRI::ArgumentList or Array. A list-like
22
+ # collection of the remaining arguments to the CLI invocation (after
23
+ # removing flags and options).
23
24
  # @param settings [Hash] A hash of configuration loaded from the relevant
24
25
  # config (r10k.yaml).
25
- def initialize(opts, argv, settings)
26
+ #
27
+ # @note All arguments will be required in the next major version
28
+ def initialize(opts, argv, settings = {})
26
29
  super
27
30
 
28
31
  requested_env = @opts[:environment] ? [@opts[:environment].gsub(/\W/, '_')] : []
32
+ @modified_envs = []
29
33
 
30
34
  @settings = @settings.merge({
31
35
  overrides: {
@@ -34,6 +38,7 @@ module R10K
34
38
  generate_types: @generate_types
35
39
  },
36
40
  modules: {
41
+ exclude_spec: settings.dig(:deploy, :exclude_spec),
37
42
  requested_modules: @argv.map.to_a,
38
43
  # force here is used to make it easier to reason about
39
44
  force: !@no_force
@@ -66,6 +71,21 @@ module R10K
66
71
 
67
72
  def visit_deployment(deployment)
68
73
  yield
74
+ ensure
75
+ if (postcmd = @settings[:postrun])
76
+ if @modified_envs.any?
77
+ if postcmd.grep('$modifiedenvs').any?
78
+ envs_to_run = @modified_envs.join(' ')
79
+ logger.debug "Running postrun command for environments #{envs_to_run}."
80
+ postcmd = postcmd.map { |e| e.gsub('$modifiedenvs', envs_to_run) }
81
+ end
82
+ subproc = R10K::Util::Subprocess.new(postcmd)
83
+ subproc.logger = logger
84
+ subproc.execute
85
+ else
86
+ logger.debug "No environments were modified, not executing postrun command."
87
+ end
88
+ end
69
89
  end
70
90
 
71
91
  def visit_source(source)
@@ -82,10 +102,15 @@ module R10K
82
102
  environment.deploy
83
103
 
84
104
  requested_mods = @settings.dig(:overrides, :modules, :requested_modules) || []
85
- generate_types = @settings.dig(:overrides, :environments, :generate_types)
86
- if generate_types && !((environment.modules.map(&:name) & requested_mods).empty?)
87
- logger.debug("Generating puppet types for environment '#{environment.dirname}'...")
88
- environment.generate_types!
105
+ # We actually synced a module in this env
106
+ if !((environment.modules.map(&:name) & requested_mods).empty?)
107
+ # Record modified environment for postrun command
108
+ @modified_envs << environment.dirname
109
+
110
+ if generate_types = @settings.dig(:overrides, :environments, :generate_types)
111
+ logger.debug("Generating puppet types for environment '#{environment.dirname}'...")
112
+ environment.generate_types!
113
+ end
89
114
  end
90
115
  end
91
116
  end
@@ -93,12 +118,16 @@ module R10K
93
118
  def allowed_initialize_opts
94
119
  super.merge(environment: true,
95
120
  cachedir: :self,
121
+ 'exclude-spec': :self,
96
122
  'no-force': :self,
97
123
  'generate-types': :self,
98
124
  'puppet-path': :self,
99
125
  'puppet-conf': :self,
100
126
  'private-key': :self,
101
- 'oauth-token': :self)
127
+ 'oauth-token': :self,
128
+ 'github-app-id': :self,
129
+ 'github-app-key': :self,
130
+ 'github-app-ttl': :self)
102
131
  end
103
132
  end
104
133
  end
@@ -44,10 +44,13 @@ module R10K
44
44
 
45
45
  overrides = {}
46
46
  overrides[:cachedir] = @opts[:cachedir] if @opts.key?(:cachedir)
47
- overrides[:deploy] = {} if @opts.key?(:'puppet-path') || @opts.key?(:'generate-types')
48
- overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] if @opts.key?(:'puppet-path')
49
- overrides[:deploy][:puppet_conf] = @opts[:'puppet-conf'] unless @opts[:'puppet-conf'].nil?
50
- overrides[:deploy][:generate_types] = @opts[:'generate-types'] if @opts.key?(:'generate-types')
47
+ if @opts.key?(:'puppet-path') || @opts.key?(:'generate-types') || @opts.key?(:'exclude-spec') || @opts.key?(:'puppet-conf')
48
+ overrides[:deploy] = {}
49
+ overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] if @opts.key?(:'puppet-path')
50
+ overrides[:deploy][:puppet_conf] = @opts[:'puppet-conf'] if @opts.key?(:'puppet-conf')
51
+ overrides[:deploy][:generate_types] = @opts[:'generate-types'] if @opts.key?(:'generate-types')
52
+ overrides[:deploy][:exclude_spec] = @opts[:'exclude-spec'] if @opts.key?(:'exclude-spec')
53
+ end
51
54
 
52
55
  with_overrides = config_settings.merge(overrides) do |key, oldval, newval|
53
56
  newval = oldval.merge(newval) if oldval.is_a? Hash
@@ -67,15 +70,20 @@ module R10K
67
70
  exit(8)
68
71
  end
69
72
 
73
+ # Set up authorization from license file if it wasn't
74
+ # already set via the config
70
75
  def setup_authorization
71
- begin
72
- license = R10K::Util::License.load
76
+ if PuppetForge::Connection.authorization.nil?
77
+ begin
78
+ license = R10K::Util::License.load
73
79
 
74
- if license.respond_to?(:authorization_token)
75
- PuppetForge::Connection.authorization = license.authorization_token
80
+ if license.respond_to?(:authorization_token)
81
+ logger.debug "Using token from license to connect to the Forge."
82
+ PuppetForge::Connection.authorization = license.authorization_token
83
+ end
84
+ rescue R10K::Error => e
85
+ logger.warn e.message
76
86
  end
77
- rescue R10K::Error => e
78
- logger.warn e.message
79
87
  end
80
88
  end
81
89
 
@@ -100,11 +108,26 @@ module R10K
100
108
  def add_credential_overrides(overrides)
101
109
  sshkey_path = @opts[:'private-key']
102
110
  token_path = @opts[:'oauth-token']
111
+ app_id = @opts[:'github-app-id']
112
+ app_private_key_path = @opts[:'github-app-key']
113
+ app_ttl = @opts[:'github-app-ttl']
103
114
 
104
115
  if sshkey_path && token_path
105
116
  raise R10K::Error, "Cannot specify both an SSH key and a token to use with this deploy."
106
117
  end
107
118
 
119
+ if sshkey_path && (app_private_key_path || app_id)
120
+ raise R10K::Error, "Cannot specify both an SSH key and an SSL key or Github App id to use with this deploy."
121
+ end
122
+
123
+ if token_path && (app_private_key_path || app_id)
124
+ raise R10K::Error, "Cannot specify both an OAuth token and an SSL key or Github App id to use with this deploy."
125
+ end
126
+
127
+ if app_id && ! app_private_key_path || app_private_key_path && ! app_id
128
+ raise R10K::Error, "Must specify both id and SSL private key to use Github App for this deploy."
129
+ end
130
+
108
131
  if sshkey_path
109
132
  overrides[:git] ||= {}
110
133
  overrides[:git][:private_key] = sshkey_path
@@ -121,6 +144,18 @@ module R10K
121
144
  repo[:oauth_token] = token_path
122
145
  end
123
146
  end
147
+ elsif app_id
148
+ overrides[:git] ||= {}
149
+ overrides[:git][:github_app_id] = app_id
150
+ overrides[:git][:github_app_key] = app_private_key_path
151
+ overrides[:git][:github_app_ttl] = app_ttl
152
+ if repo_settings = overrides[:git][:repositories]
153
+ repo_settings.each do |repo|
154
+ repo[:github_app_id] = app_id
155
+ repo[:github_app_key] = app_private_key_path
156
+ repo[:github_app_ttl] = app_ttl
157
+ end
158
+ end
124
159
  end
125
160
 
126
161
  overrides
@@ -24,6 +24,7 @@ module R10K::CLI
24
24
  option nil, :cachedir, 'Specify a cachedir, overriding the value in config', argument: :required
25
25
  flag nil, :'no-force', 'Prevent the overwriting of local module modifications'
26
26
  flag nil, :'generate-types', 'Run `puppet generate types` after updating an environment'
27
+ flag nil, :'exclude-spec', 'Exclude the module\'s spec dir from deployment'
27
28
  option nil, :'puppet-path', 'Path to puppet executable', argument: :required do |value, cmd|
28
29
  unless File.executable? value
29
30
  $stderr.puts "The specified puppet executable #{value} is not executable."
@@ -34,6 +35,9 @@ module R10K::CLI
34
35
  option nil, :'puppet-conf', 'Path to puppet.conf', argument: :required
35
36
  option nil, :'private-key', 'Path to SSH key to use when cloning. Only valid with rugged provider', argument: :required
36
37
  option nil, :'oauth-token', 'Path to OAuth token to use when cloning. Only valid with rugged provider', argument: :required
38
+ option nil, :'github-app-id', 'Github App id. Only valid with rugged provider', argument: :required
39
+ option nil, :'github-app-key', 'Github App private key. Only valid with rugged provider', argument: :required
40
+ option nil, :'github-app-ttl', 'Github App token expiration, in seconds. Only valid with rugged provider', default: "120", argument: :optional
37
41
 
38
42
  run do |opts, args, cmd|
39
43
  puts cmd.help(:verbose => opts[:verbose])
data/lib/r10k/git.rb CHANGED
@@ -135,6 +135,9 @@ module R10K
135
135
 
136
136
  def_setting_attr :private_key
137
137
  def_setting_attr :oauth_token
138
+ def_setting_attr :github_app_id
139
+ def_setting_attr :github_app_key
140
+ def_setting_attr :github_app_ttl
138
141
  def_setting_attr :proxy
139
142
  def_setting_attr :username
140
143
  def_setting_attr :repositories, {}
@@ -111,6 +111,6 @@ class R10K::Git::Cache
111
111
 
112
112
  # Reformat the remote name into something that can be used as a directory
113
113
  def sanitized_dirname
114
- @sanitized_dirname ||= @remote.gsub(/[^@\w\.-]/, '-')
114
+ @sanitized_dirname ||= @remote.gsub(/(\w+:\/\/)(.*)(@)/, '\1').gsub(/[^@\w\.-]/, '-')
115
115
  end
116
116
  end
@@ -1,6 +1,10 @@
1
1
  require 'r10k/git/rugged'
2
2
  require 'r10k/git/errors'
3
3
  require 'r10k/logging'
4
+ require 'json'
5
+ require 'jwt'
6
+ require 'net/http'
7
+ require 'openssl'
4
8
 
5
9
  # Generate credentials for secured remote connections.
6
10
  #
@@ -62,15 +66,29 @@ class R10K::Git::Rugged::Credentials
62
66
 
63
67
  def get_plaintext_credentials(url, username_from_url)
64
68
  per_repo_oauth_token = nil
69
+ per_repo_github_app_id = nil
70
+ per_repo_github_app_key = nil
71
+ per_repo_github_app_ttl = nil
72
+
65
73
  if per_repo_settings = R10K::Git.get_repo_settings(url)
66
74
  per_repo_oauth_token = per_repo_settings[:oauth_token]
75
+ per_repo_github_app_id = per_repo_settings[:github_app_id]
76
+ per_repo_github_app_key = per_repo_settings[:github_app_key]
77
+ per_repo_github_app_ttl = per_repo_settings[:github_app_ttl]
67
78
  end
68
79
 
80
+ app_id = per_repo_github_app_id || R10K::Git.settings[:github_app_id]
81
+ app_key = per_repo_github_app_key || R10K::Git.settings[:github_app_key]
82
+ app_ttl = per_repo_github_app_ttl || R10K::Git.settings[:github_app_ttl]
83
+
69
84
  if token_path = per_repo_oauth_token || R10K::Git.settings[:oauth_token]
70
85
  @oauth_token ||= extract_token(token_path, url)
71
86
 
72
87
  user = 'x-oauth-token'
73
88
  password = @oauth_token
89
+ elsif app_id && app_key && app_ttl
90
+ user = 'x-access-token'
91
+ password = github_app_token(app_id, app_key, app_ttl)
74
92
  else
75
93
  user = get_git_username(url, username_from_url)
76
94
  password = URI.parse(url).password || ''
@@ -125,4 +143,63 @@ class R10K::Git::Rugged::Credentials
125
143
 
126
144
  user
127
145
  end
146
+
147
+ def github_app_token(app_id, private_key, ttl)
148
+ raise R10K::Git::GitError, _('Github App id contains invalid characters.') unless app_id =~ /^\d+$/
149
+ raise R10K::Git::GitError, _('Github App token ttl contains invalid characters.') unless ttl =~ /^\d+$/
150
+ raise R10K::Git::GitError, _('Github App key is missing or unreadable') unless File.readable?(private_key)
151
+
152
+ begin
153
+ ssl_key = OpenSSL::PKey::RSA.new(File.read(private_key).strip)
154
+ unless ssl_key.private?
155
+ raise R10K::Git::GitError, _('Github App key is not a valid SSL private key')
156
+ end
157
+ rescue OpenSSL::PKey::RSAError
158
+ raise R10K::Git::GitError, _('Github App key is not a valid SSL key')
159
+ end
160
+
161
+ logger.debug2 _("Using Github App id %{app_id} with SSL key from %{key_path}") % { key_path: private_key, app_id: app_id }
162
+
163
+ jwt_issue_time = Time.now.to_i - 60
164
+ jwt_exp_time = (jwt_issue_time + 60) + ttl.to_i
165
+ payload = { iat: jwt_issue_time, exp: jwt_exp_time, iss: app_id }
166
+ jwt = JWT.encode(payload, ssl_key, "RS256")
167
+
168
+ get = URI.parse("https://api.github.com/app/installations")
169
+ get_request = Net::HTTP::Get.new(get)
170
+ get_request["Authorization"] = "Bearer #{jwt}"
171
+ get_request["Accept"] = "application/vnd.github.v3+json"
172
+ get_req_options = { use_ssl: get.scheme == "https", }
173
+ get_response = Net::HTTP.start(get.hostname, get.port, get_req_options) do |http|
174
+ http.request(get_request)
175
+ end
176
+
177
+ unless (get_response.class < Net::HTTPSuccess)
178
+ logger.debug2 _("Unexpected response code: #{get_response.code}\nResponse body: #{get_response.body}")
179
+ raise R10K::Git::GitError, _("Error using private key to get Github App access token from url")
180
+ end
181
+
182
+ access_tokens_url = JSON.parse(get_response.body)[0]['access_tokens_url']
183
+
184
+ post = URI.parse(access_tokens_url)
185
+ post_request = Net::HTTP::Post.new(post)
186
+ post_request["Authorization"] = "Bearer #{jwt}"
187
+ post_request["Accept"] = "application/vnd.github.v3+json"
188
+ post_req_options = { use_ssl: post.scheme == "https", }
189
+ post_response = Net::HTTP.start(post.hostname, post.port, post_req_options) do |http|
190
+ http.request(post_request)
191
+ end
192
+
193
+ unless (post_response.class < Net::HTTPSuccess)
194
+ logger.debug2 _("Unexpected response code: #{post_response.code}\nResponse body: #{post_response.body}")
195
+ raise R10K::Git::GitError, _("Error using private key to generate access token from #{access_token_url}")
196
+ end
197
+
198
+ token = JSON.parse(post_response.body)['token']
199
+
200
+ raise R10K::Git::GitError, _("Github App token contains invalid characters.") unless valid_token?(token)
201
+
202
+ logger.debug2 _("Github App token generated, expires at: %{expire}") % {expire: JSON.parse(post_response.body)['expires_at']}
203
+ token
204
+ end
128
205
  end
@@ -93,6 +93,7 @@ class R10K::Git::StatefulRepository
93
93
  # @api private
94
94
  def sync_cache?(ref)
95
95
  return true if !@cache.exist?
96
+ return true if ref == 'HEAD'
96
97
  return true if !([:commit, :tag].include? @cache.ref_type(ref))
97
98
  return false
98
99
  end
@@ -56,6 +56,9 @@ module R10K
56
56
  with_setting(:proxy) { |value| R10K::Git.settings[:proxy] = value }
57
57
  with_setting(:repositories) { |value| R10K::Git.settings[:repositories] = value }
58
58
  with_setting(:oauth_token) { |value| R10K::Git.settings[:oauth_token] = value }
59
+ with_setting(:github_app_id) { |value| R10K::Git.settings[:github_app_id] = value }
60
+ with_setting(:github_app_key) { |value| R10K::Git.settings[:github_app_key] = value }
61
+ with_setting(:github_app_ttl) { |value| R10K::Git.settings[:github_app_ttl] = value }
59
62
  end
60
63
  end
61
64
 
@@ -63,6 +66,13 @@ module R10K
63
66
  def call
64
67
  with_setting(:baseurl) { |value| PuppetForge.host = value }
65
68
  with_setting(:proxy) { |value| PuppetForge::Connection.proxy = value }
69
+ with_setting(:authorization_token) { |value|
70
+ if @settings[:baseurl]
71
+ PuppetForge::Connection.authorization = value
72
+ else
73
+ raise R10K::Error, "Cannot specify a Forge authorization token without configuring a custom baseurl."
74
+ end
75
+ }
66
76
  end
67
77
  end
68
78
  end
@@ -35,6 +35,10 @@ class R10K::Module::Base
35
35
  # @return [String] Where the module was sourced from. E.g., "Puppetfile"
36
36
  attr_accessor :origin
37
37
 
38
+ # @!attribute [rw] spec_deletable
39
+ # @return [Boolean] set this to true if the spec dir can be safely removed, ie in the moduledir
40
+ attr_accessor :spec_deletable
41
+
38
42
  # There's been some churn over `author` vs `owner` and `full_name` over
39
43
  # `title`, so in the short run it's easier to support both and deprecate one
40
44
  # later.
@@ -52,6 +56,9 @@ class R10K::Module::Base
52
56
  @path = Pathname.new(File.join(@dirname, @name))
53
57
  @environment = environment
54
58
  @overrides = args.delete(:overrides) || {}
59
+ @spec_deletable = true
60
+ @exclude_spec = args.delete(:exclude_spec)
61
+ @exclude_spec = @overrides[:modules].delete(:exclude_spec) if @overrides.dig(:modules, :exclude_spec)
55
62
  @origin = 'external' # Expect Puppetfile or R10k::Environment to set this to a specific value
56
63
 
57
64
  @requested_modules = @overrides.dig(:modules, :requested_modules) || []
@@ -64,6 +71,36 @@ class R10K::Module::Base
64
71
  path.to_s
65
72
  end
66
73
 
74
+ # Delete the spec dir if @exclude_spec has been set to true and @spec_deletable is also true
75
+ def maybe_delete_spec_dir
76
+ if @exclude_spec
77
+ if @spec_deletable
78
+ delete_spec_dir
79
+ else
80
+ logger.info _("Spec dir for #{@title} will not be deleted because it is not in the moduledir")
81
+ end
82
+ end
83
+ end
84
+
85
+ # Actually remove the spec dir
86
+ def delete_spec_dir
87
+ spec_path = @path + 'spec'
88
+ if spec_path.symlink?
89
+ spec_path = spec_path.realpath
90
+ end
91
+ if spec_path.directory?
92
+ logger.debug2 _("Deleting spec data at #{spec_path}")
93
+ # Use the secure flag for the #rm_rf method to avoid security issues
94
+ # involving TOCTTOU(time of check to time of use); more details here:
95
+ # https://ruby-doc.org/stdlib-2.7.0/libdoc/fileutils/rdoc/FileUtils.html#method-c-rm_rf
96
+ # Additionally, #rm_rf also has problems in windows with with symlink targets
97
+ # also being deleted; this should be revisted if Windows becomes higher priority.
98
+ FileUtils.rm_rf(spec_path, secure: true)
99
+ else
100
+ logger.debug2 _("No spec dir detected at #{spec_path}, skipping deletion")
101
+ end
102
+ end
103
+
67
104
  # Synchronize this module with the indicated state.
68
105
  # @param [Hash] opts Deprecated
69
106
  def sync(opts={})
@@ -50,7 +50,7 @@ class R10K::Module::Forge < R10K::Module::Base
50
50
  :version => :expected_version,
51
51
  :source => ::R10K::Util::Setopts::Ignore,
52
52
  :type => ::R10K::Util::Setopts::Ignore,
53
- })
53
+ }, :raise_on_unhandled => false)
54
54
 
55
55
  @expected_version ||= current_version || :latest
56
56
 
@@ -68,6 +68,7 @@ class R10K::Module::Forge < R10K::Module::Base
68
68
  when :mismatched
69
69
  reinstall
70
70
  end
71
+ maybe_delete_spec_dir
71
72
  end
72
73
  end
73
74
 
@@ -83,7 +84,11 @@ class R10K::Module::Forge < R10K::Module::Base
83
84
  def expected_version
84
85
  if @expected_version == :latest
85
86
  begin
86
- @expected_version = @v3_module.current_release.version
87
+ if @v3_module.current_release
88
+ @expected_version = @v3_module.current_release.version
89
+ else
90
+ raise PuppetForge::ReleaseNotFound, _("The module %{title} does not appear to have any published releases, cannot determine latest version.") % { title: @title }
91
+ end
87
92
  rescue Faraday::ResourceNotFound => e
88
93
  raise PuppetForge::ReleaseNotFound, _("The module %{title} does not exist on %{url}.") % {title: @title, url: PuppetForge::V3::Release.conn.url_prefix}, e.backtrace
89
94
  end
@@ -52,7 +52,7 @@ class R10K::Module::Git < R10K::Module::Base
52
52
  :git => :remote,
53
53
  :default_branch => :default_ref,
54
54
  :default_branch_override => :default_override_ref,
55
- })
55
+ }, :raise_on_unhandled => false)
56
56
 
57
57
  force = @overrides.dig(:modules, :force)
58
58
  @force = force == false ? false : true
@@ -86,6 +86,7 @@ class R10K::Module::Git < R10K::Module::Base
86
86
  def sync(opts={})
87
87
  force = opts[:force] || @force
88
88
  @repo.sync(version, force) if should_sync?
89
+ maybe_delete_spec_dir
89
90
  end
90
91
 
91
92
  def status