r10k 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +4 -1
  3. data/.github/workflows/release.yml +3 -2
  4. data/.github/workflows/rspec_tests.yml +1 -1
  5. data/.travis.yml +8 -1
  6. data/CHANGELOG.mkd +10 -0
  7. data/CODEOWNERS +1 -1
  8. data/doc/common-patterns.mkd +1 -0
  9. data/doc/dynamic-environments/configuration.mkd +90 -43
  10. data/doc/dynamic-environments/usage.mkd +7 -7
  11. data/doc/puppetfile.mkd +23 -3
  12. data/docker/Gemfile +1 -1
  13. data/docker/Makefile +4 -3
  14. data/docker/docker-compose.yml +18 -0
  15. data/docker/r10k/Dockerfile +1 -1
  16. data/docker/r10k/docker-entrypoint.sh +0 -1
  17. data/docker/r10k/release.Dockerfile +1 -1
  18. data/docker/spec/dockerfile_spec.rb +26 -32
  19. data/integration/tests/git_source/git_source_repeated_remote.rb +2 -2
  20. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module.rb +2 -1
  21. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module_static.rb +2 -1
  22. data/integration/tests/user_scenario/basic_workflow/multi_source_custom_forge_git_module.rb +1 -1
  23. data/integration/tests/user_scenario/basic_workflow/single_env_custom_forge_git_module.rb +2 -1
  24. data/lib/r10k/action/deploy/display.rb +9 -3
  25. data/lib/r10k/action/deploy/environment.rb +36 -14
  26. data/lib/r10k/cli/deploy.rb +5 -3
  27. data/lib/r10k/environment/base.rb +1 -1
  28. data/lib/r10k/environment/git.rb +17 -2
  29. data/lib/r10k/environment/name.rb +22 -4
  30. data/lib/r10k/environment/svn.rb +11 -2
  31. data/lib/r10k/environment/with_modules.rb +1 -1
  32. data/lib/r10k/git/rugged/credentials.rb +22 -15
  33. data/lib/r10k/module/forge.rb +15 -3
  34. data/lib/r10k/module/git.rb +24 -23
  35. data/lib/r10k/module/local.rb +1 -1
  36. data/lib/r10k/module/svn.rb +14 -11
  37. data/lib/r10k/settings.rb +6 -1
  38. data/lib/r10k/source/base.rb +5 -0
  39. data/lib/r10k/source/git.rb +4 -1
  40. data/lib/r10k/source/hash.rb +4 -2
  41. data/lib/r10k/source/svn.rb +5 -1
  42. data/lib/r10k/util/setopts.rb +33 -12
  43. data/lib/r10k/version.rb +1 -1
  44. data/locales/r10k.pot +22 -18
  45. data/r10k.gemspec +1 -1
  46. data/spec/unit/action/deploy/display_spec.rb +4 -0
  47. data/spec/unit/action/deploy/environment_spec.rb +111 -10
  48. data/spec/unit/environment/git_spec.rb +16 -0
  49. data/spec/unit/environment/name_spec.rb +28 -0
  50. data/spec/unit/environment/svn_spec.rb +12 -0
  51. data/spec/unit/git/rugged/credentials_spec.rb +10 -0
  52. data/spec/unit/module/forge_spec.rb +6 -0
  53. data/spec/unit/module/git_spec.rb +1 -1
  54. data/spec/unit/module_spec.rb +59 -9
  55. data/spec/unit/util/setopts_spec.rb +25 -1
  56. metadata +5 -11
  57. data/azure-pipelines.yml +0 -87
@@ -24,7 +24,7 @@ class R10K::Environment::WithModules < R10K::Environment::Base
24
24
  # @param options [String] :moduledir The path to install modules to
25
25
  # @param options [Hash] :modules Modules to add to the environment
26
26
  def initialize(name, basedir, dirname, options = {})
27
- super(name, basedir, dirname, options)
27
+ super
28
28
 
29
29
  @managed_content = {}
30
30
  @modules = []
@@ -67,22 +67,10 @@ class R10K::Git::Rugged::Credentials
67
67
  end
68
68
 
69
69
  if token_path = per_repo_oauth_token || R10K::Git.settings[:oauth_token]
70
- if token_path == '-'
71
- token = $stdin.read.strip
72
- logger.debug2 _("Using OAuth token from stdin for URL %{url}") % { url: url }
73
- elsif File.readable?(token_path)
74
- token = File.read(token_path).strip
75
- logger.debug2 _("Using OAuth token from %{token_path} for URL %{url}") % { token_path: token_path, url: url }
76
- else
77
- raise R10K::Git::GitError, _("%{path} is missing or unreadable, cannot load OAuth token") % { path: token_path }
78
- end
79
-
80
- unless valid_token?(token)
81
- raise R10K::Git::GitError, _("Supplied OAuth token contains invalid characters.")
82
- end
70
+ @oauth_token ||= extract_token(token_path, url)
83
71
 
84
72
  user = 'x-oauth-token'
85
- password = token
73
+ password = @oauth_token
86
74
  else
87
75
  user = get_git_username(url, username_from_url)
88
76
  password = URI.parse(url).password || ''
@@ -90,10 +78,29 @@ class R10K::Git::Rugged::Credentials
90
78
  Rugged::Credentials::UserPassword.new(username: user, password: password)
91
79
  end
92
80
 
81
+ def extract_token(token_path, url)
82
+ if token_path == '-'
83
+ token = $stdin.read.strip
84
+ logger.debug2 _("Using OAuth token from stdin for URL %{url}") % { url: url }
85
+ elsif File.readable?(token_path)
86
+ token = File.read(token_path).strip
87
+ logger.debug2 _("Using OAuth token from %{token_path} for URL %{url}") % { token_path: token_path, url: url }
88
+ else
89
+ raise R10K::Git::GitError, _("%{path} is missing or unreadable, cannot load OAuth token") % { path: token_path }
90
+ end
91
+
92
+ unless valid_token?(token)
93
+ raise R10K::Git::GitError, _("Supplied OAuth token contains invalid characters.")
94
+ end
95
+
96
+ token
97
+ end
98
+
93
99
  # This regex is the only real requirement for OAuth token format,
94
100
  # per https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
101
+ # Bitbucket's tokens also can include an underscore, so that is added here.
95
102
  def valid_token?(token)
96
- return token =~ /^[\w\-\.~\+\/]+$/
103
+ return token =~ /^[\w\-\.~_\+\/]+$/
97
104
  end
98
105
 
99
106
  def get_default_credentials(url, username_from_url)
@@ -13,7 +13,7 @@ class R10K::Module::Forge < R10K::Module::Base
13
13
  R10K::Module.register(self)
14
14
 
15
15
  def self.implement?(name, args)
16
- !!(name.match %r[\w+[/-]\w+]) && valid_version?(args)
16
+ (args.is_a?(Hash) && args[:type].to_s == 'forge') || (!!(name.match %r[\w+[/-]\w+]) && valid_version?(args))
17
17
  end
18
18
 
19
19
  def self.valid_version?(expected_version)
@@ -32,13 +32,25 @@ class R10K::Module::Forge < R10K::Module::Base
32
32
 
33
33
  include R10K::Logging
34
34
 
35
- def initialize(title, dirname, expected_version, environment=nil)
35
+ include R10K::Util::Setopts
36
+
37
+ def initialize(title, dirname, opts, environment=nil)
36
38
  super
37
39
 
38
40
  @metadata_file = R10K::Module::MetadataFile.new(path + 'metadata.json')
39
41
  @metadata = @metadata_file.read
40
42
 
41
- @expected_version = expected_version || current_version || :latest
43
+ if opts.is_a?(Hash)
44
+ setopts(opts, {
45
+ # Standard option interface
46
+ :version => :expected_version,
47
+ :source => ::R10K::Util::Setopts::Ignore,
48
+ :type => ::R10K::Util::Setopts::Ignore,
49
+ })
50
+ else
51
+ @expected_version = opts || current_version || :latest
52
+ end
53
+
42
54
  @v3_module = PuppetForge::V3::Module.new(:slug => @title)
43
55
  end
44
56
 
@@ -8,7 +8,7 @@ class R10K::Module::Git < R10K::Module::Base
8
8
  R10K::Module.register(self)
9
9
 
10
10
  def self.implement?(name, args)
11
- args.is_a? Hash and args.has_key?(:git)
11
+ args.is_a?(Hash) && (args.has_key?(:git) || args[:type].to_s == 'git')
12
12
  rescue
13
13
  false
14
14
  end
@@ -33,10 +33,31 @@ class R10K::Module::Git < R10K::Module::Base
33
33
  # @return [String]
34
34
  attr_reader :default_override_ref
35
35
 
36
- def initialize(title, dirname, args, environment=nil)
36
+ include R10K::Util::Setopts
37
+
38
+ def initialize(title, dirname, opts, environment=nil)
37
39
  super
40
+ setopts(opts, {
41
+ # Standard option interface
42
+ :version => :desired_ref,
43
+ :source => :remote,
44
+ :type => ::R10K::Util::Setopts::Ignore,
45
+
46
+ # Type-specific options
47
+ :branch => :desired_ref,
48
+ :tag => :desired_ref,
49
+ :commit => :desired_ref,
50
+ :ref => :desired_ref,
51
+ :git => :remote,
52
+ :default_branch => :default_ref,
53
+ :default_branch_override => :default_override_ref,
54
+ })
55
+
56
+ @desired_ref ||= 'master'
38
57
 
39
- parse_options(@args)
58
+ if @desired_ref == :control_branch && @environment && @environment.respond_to?(:ref)
59
+ @desired_ref = @environment.ref
60
+ end
40
61
 
41
62
  @repo = R10K::Git::StatefulRepository.new(@remote, @dirname, @name)
42
63
  end
@@ -103,24 +124,4 @@ class R10K::Module::Git < R10K::Module::Base
103
124
  raise ArgumentError, _(msg.join(' ')) % vars
104
125
  end
105
126
  end
106
-
107
- def parse_options(options)
108
- ref_opts = [:branch, :tag, :commit, :ref]
109
- known_opts = [:git, :default_branch, :default_branch_override] + ref_opts
110
-
111
- unhandled = options.keys - known_opts
112
- unless unhandled.empty?
113
- raise ArgumentError, _("Unhandled options %{unhandled} specified for %{class}") % {unhandled: unhandled, class: self.class}
114
- end
115
-
116
- @remote = options[:git]
117
-
118
- @desired_ref = ref_opts.find { |key| break options[key] if options.has_key?(key) } || 'master'
119
- @default_ref = options[:default_branch]
120
- @default_override_ref = options[:default_branch_override]
121
-
122
- if @desired_ref == :control_branch && @environment && @environment.respond_to?(:ref)
123
- @desired_ref = @environment.ref
124
- end
125
- end
126
127
  end
@@ -9,7 +9,7 @@ class R10K::Module::Local < R10K::Module::Base
9
9
  R10K::Module.register(self)
10
10
 
11
11
  def self.implement?(name, args)
12
- args.is_a?(Hash) && args[:local]
12
+ args.is_a?(Hash) && (args[:local] || args[:type].to_s == 'local')
13
13
  end
14
14
 
15
15
  include R10K::Logging
@@ -7,7 +7,7 @@ class R10K::Module::SVN < R10K::Module::Base
7
7
  R10K::Module.register(self)
8
8
 
9
9
  def self.implement?(name, args)
10
- args.is_a? Hash and args.has_key? :svn
10
+ args.is_a?(Hash) && (args.has_key?(:svn) || args[:type].to_s == 'svn')
11
11
  end
12
12
 
13
13
  # @!attribute [r] expected_revision
@@ -36,18 +36,21 @@ class R10K::Module::SVN < R10K::Module::Base
36
36
 
37
37
  include R10K::Util::Setopts
38
38
 
39
- INITIALIZE_OPTS = {
40
- :svn => :url,
41
- :rev => :expected_revision,
42
- :revision => :expected_revision,
43
- :username => :self,
44
- :password => :self
45
- }
46
-
47
39
  def initialize(name, dirname, opts, environment=nil)
48
40
  super
49
-
50
- setopts(opts, INITIALIZE_OPTS)
41
+ setopts(opts, {
42
+ # Standard option interface
43
+ :source => :url,
44
+ :version => :expected_revision,
45
+ :type => ::R10K::Util::Setopts::Ignore,
46
+
47
+ # Type-specific options
48
+ :svn => :url,
49
+ :rev => :expected_revision,
50
+ :revision => :expected_revision,
51
+ :username => :self,
52
+ :password => :self
53
+ })
51
54
 
52
55
  @working_dir = R10K::SVN::WorkingDir.new(@path, :username => @username, :password => @password)
53
56
  end
data/lib/r10k/settings.rb CHANGED
@@ -122,11 +122,16 @@ module R10K
122
122
  end,
123
123
  }),
124
124
 
125
- Definition.new(:purge_whitelist, {
125
+ Definition.new(:purge_allowlist, {
126
126
  :desc => "A list of filename patterns to be excluded from any purge operations. Patterns are matched relative to the root of each deployed environment, if you want a pattern to match recursively you need to use the '**' glob in your pattern. Basic shell style globs are supported.",
127
127
  :default => [],
128
128
  }),
129
129
 
130
+ Definition.new(:purge_whitelist, {
131
+ :desc => "Deprecated; please use purge_allowlist instead. This setting will be removed in a future version.",
132
+ :default => [],
133
+ }),
134
+
130
135
  Definition.new(:generate_types, {
131
136
  :desc => "Controls whether to generate puppet types after deploying an environment. Defaults to false.",
132
137
  :default => false,
@@ -31,10 +31,15 @@ class R10K::Source::Base
31
31
  # @option options [Boolean, String] :prefix If a String this becomes the prefix.
32
32
  # If true, will use the source name as the prefix. All sources should respect this option.
33
33
  # Defaults to false for no environment prefix.
34
+ # @option options [String] :strip_component If a string, this value will be
35
+ # removed from the beginning of each generated environment's name, if
36
+ # present. If the string is contained within two "/" characters, it will
37
+ # be treated as a regular expression.
34
38
  def initialize(name, basedir, options = {})
35
39
  @name = name
36
40
  @basedir = Pathname.new(basedir).cleanpath.to_s
37
41
  @prefix = options.delete(:prefix)
42
+ @strip_component = options.delete(:strip_component)
38
43
  @puppetfile_name = options.delete(:puppetfile_name)
39
44
  @options = options
40
45
  end
@@ -145,7 +145,10 @@ class R10K::Source::Git < R10K::Source::Base
145
145
  private
146
146
 
147
147
  def branch_names
148
- opts = {:prefix => @prefix, :invalid => @invalid_branches, :source => @name}
148
+ opts = {prefix: @prefix,
149
+ invalid: @invalid_branches,
150
+ source: @name,
151
+ strip_component: @strip_component}
149
152
  branches = @cache.branches
150
153
  if @ignore_branch_prefixes && !@ignore_branch_prefixes.empty?
151
154
  branches = filter_branches_by_regexp(branches, @ignore_branch_prefixes)
@@ -152,8 +152,10 @@ class R10K::Source::Hash < R10K::Source::Base
152
152
  R10K::Util::SymbolizeKeys.symbolize_keys!(opts)
153
153
  memo.merge({
154
154
  name => opts.merge({
155
- :basedir => @basedir,
156
- :dirname => R10K::Environment::Name.new(name, {prefix: @prefix, source: @name}).dirname
155
+ basedir: @basedir,
156
+ dirname: R10K::Environment::Name.new(name, {prefix: @prefix,
157
+ source: @name,
158
+ strip_component: @strip_component}).dirname
157
159
  })
158
160
  })
159
161
  end
@@ -121,7 +121,11 @@ class R10K::Source::SVN < R10K::Source::Base
121
121
 
122
122
  def names_and_paths
123
123
  branches = []
124
- opts = {:prefix => @prefix, :correct => false, :validate => false, :source => @name}
124
+ opts = {prefix: @prefix,
125
+ correct: false,
126
+ validate: false,
127
+ source: @name,
128
+ strip_component: @strip_component}
125
129
  branches << [R10K::Environment::Name.new('production', opts), "#{@remote}/trunk"]
126
130
  additional_branch_names = @svn_remote.branches
127
131
  if @ignore_branch_prefixes && !@ignore_branch_prefixes.empty?
@@ -7,6 +7,10 @@ module R10K
7
7
  # supports Ruby 1.8.7+ we cannot use that functionality.
8
8
  module Setopts
9
9
 
10
+ class Ignore; end
11
+
12
+ include R10K::Logging
13
+
10
14
  private
11
15
 
12
16
  # @param opts [Hash]
@@ -31,22 +35,39 @@ module R10K
31
35
  # setopts(opts, allowed)
32
36
  # @trace # => nil
33
37
  #
34
- def setopts(opts, allowed)
38
+ def setopts(opts, allowed, raise_on_unhandled: true)
39
+ processed_vars = {}
35
40
  opts.each_pair do |key, value|
36
41
  if allowed.key?(key)
37
- rhs = allowed[key]
38
- case rhs
39
- when NilClass, FalseClass
40
- # Ignore nil options
41
- when :self, TrueClass
42
- # tr here is because instance variables cannot have hyphens in their names.
43
- instance_variable_set("@#{key}".tr('-','_').to_sym, value)
44
- else
45
- # tr here same as previous
46
- instance_variable_set("@#{rhs}".tr('-','_').to_sym, value)
42
+ # Ignore nil options and explicit ignore param
43
+ next unless rhs = allowed[key]
44
+ next if rhs == ::R10K::Util::Setopts::Ignore
45
+
46
+ var = case rhs
47
+ when :self, TrueClass
48
+ # tr here is because instance variables cannot have hyphens in their names.
49
+ "@#{key}".tr('-','_').to_sym
50
+ else
51
+ # tr here same as previous
52
+ "@#{rhs}".tr('-','_').to_sym
53
+ end
54
+
55
+ if processed_vars.include?(var)
56
+ # This should be a raise, but that would be a behavior change and
57
+ # should happen on a SemVer boundry.
58
+ logger.warn _("%{class_name} parameters '%{a}' and '%{b}' conflict. Specify one or the other, but not both" \
59
+ % {class_name: self.class.name, a: processed_vars[var], b: key})
47
60
  end
61
+
62
+ instance_variable_set(var, value)
63
+ processed_vars[var] = key
48
64
  else
49
- raise ArgumentError, _("%{class_name} cannot handle option '%{key}'") % {class_name: self.class.name, key: key}
65
+ err_str = _("%{class_name} cannot handle option '%{key}'") % {class_name: self.class.name, key: key}
66
+ if raise_on_unhandled
67
+ raise ArgumentError, err_str
68
+ else
69
+ logger.warn(err_str)
70
+ end
50
71
  end
51
72
  end
52
73
  end
data/lib/r10k/version.rb CHANGED
@@ -2,5 +2,5 @@ module R10K
2
2
  # When updating to a new major (X) or minor (Y) version, include `#major` or
3
3
  # `#minor` (respectively) in your commit message to trigger the appropriate
4
4
  # release. Otherwise, a new patch (Z) version will be released.
5
- VERSION = '3.8.0'
5
+ VERSION = '3.9.0'
6
6
  end
data/locales/r10k.pot CHANGED
@@ -6,11 +6,11 @@
6
6
  #, fuzzy
7
7
  msgid ""
8
8
  msgstr ""
9
- "Project-Id-Version: r10k 3.4.1-133-g2007a86\n"
9
+ "Project-Id-Version: r10k 3.4.1-151-g93d38cb\n"
10
10
  "\n"
11
11
  "Report-Msgid-Bugs-To: docs@puppetlabs.com\n"
12
- "POT-Creation-Date: 2021-02-12 02:00+0000\n"
13
- "PO-Revision-Date: 2021-02-12 02:00+0000\n"
12
+ "POT-Creation-Date: 2021-03-15 17:00+0000\n"
13
+ "PO-Revision-Date: 2021-03-15 17:00+0000\n"
14
14
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
15
15
  "Language-Team: LANGUAGE <LL@li.org>\n"
16
16
  "Language: \n"
@@ -103,10 +103,14 @@ msgstr ""
103
103
  msgid "Unable to load sources; the supplied configuration does not define the 'sources' key"
104
104
  msgstr ""
105
105
 
106
- #: ../lib/r10k/environment/base.rb:61 ../lib/r10k/environment/base.rb:77 ../lib/r10k/environment/base.rb:86 ../lib/r10k/source/base.rb:64
106
+ #: ../lib/r10k/environment/base.rb:61 ../lib/r10k/environment/base.rb:77 ../lib/r10k/environment/base.rb:86 ../lib/r10k/source/base.rb:69
107
107
  msgid "%{class} has not implemented method %{method}"
108
108
  msgstr ""
109
109
 
110
+ #: ../lib/r10k/environment/name.rb:78
111
+ msgid "Improper configuration value given for strip_component setting in %{src} source. Value must be a string, a /regex/, false, or omitted. Got \"%{val}\" (%{type})"
112
+ msgstr ""
113
+
110
114
  #: ../lib/r10k/environment/with_modules.rb:60
111
115
  msgid "Environment and %{src} both define the \"%{name}\" module"
112
116
  msgstr ""
@@ -231,31 +235,31 @@ msgstr ""
231
235
  msgid "Unable to use SSH key auth for %{url}: private key %{private_key} is missing or unreadable"
232
236
  msgstr ""
233
237
 
234
- #: ../lib/r10k/git/rugged/credentials.rb:72
238
+ #: ../lib/r10k/git/rugged/credentials.rb:84
235
239
  msgid "Using OAuth token from stdin for URL %{url}"
236
240
  msgstr ""
237
241
 
238
- #: ../lib/r10k/git/rugged/credentials.rb:75
242
+ #: ../lib/r10k/git/rugged/credentials.rb:87
239
243
  msgid "Using OAuth token from %{token_path} for URL %{url}"
240
244
  msgstr ""
241
245
 
242
- #: ../lib/r10k/git/rugged/credentials.rb:77
246
+ #: ../lib/r10k/git/rugged/credentials.rb:89
243
247
  msgid "%{path} is missing or unreadable, cannot load OAuth token"
244
248
  msgstr ""
245
249
 
246
- #: ../lib/r10k/git/rugged/credentials.rb:81
250
+ #: ../lib/r10k/git/rugged/credentials.rb:93
247
251
  msgid "Supplied OAuth token contains invalid characters."
248
252
  msgstr ""
249
253
 
250
- #: ../lib/r10k/git/rugged/credentials.rb:110
254
+ #: ../lib/r10k/git/rugged/credentials.rb:117
251
255
  msgid "URL %{url} includes the username %{username}, using that user for authentication."
252
256
  msgstr ""
253
257
 
254
- #: ../lib/r10k/git/rugged/credentials.rb:113
258
+ #: ../lib/r10k/git/rugged/credentials.rb:120
255
259
  msgid "URL %{url} did not specify a user, using %{user} from configuration"
256
260
  msgstr ""
257
261
 
258
- #: ../lib/r10k/git/rugged/credentials.rb:116
262
+ #: ../lib/r10k/git/rugged/credentials.rb:123
259
263
  msgid "URL %{url} did not specify a user, using current user %{user}"
260
264
  msgstr ""
261
265
 
@@ -327,18 +331,14 @@ msgstr ""
327
331
  msgid "Module name (%{title}) must match either 'modulename' or 'owner/modulename'"
328
332
  msgstr ""
329
333
 
330
- #: ../lib/r10k/module/forge.rb:70 ../lib/r10k/module/forge.rb:99
334
+ #: ../lib/r10k/module/forge.rb:81 ../lib/r10k/module/forge.rb:110
331
335
  msgid "The module %{title} does not exist on %{url}."
332
336
  msgstr ""
333
337
 
334
- #: ../lib/r10k/module/forge.rb:174
338
+ #: ../lib/r10k/module/forge.rb:185
335
339
  msgid "Forge module names must match 'owner/modulename', instead got #{title}"
336
340
  msgstr ""
337
341
 
338
- #: ../lib/r10k/module/git.rb:113
339
- msgid "Unhandled options %{unhandled} specified for %{class}"
340
- msgstr ""
341
-
342
342
  #: ../lib/r10k/module/local.rb:34
343
343
  msgid "Module %{title} is a local module, always indicating synced."
344
344
  msgstr ""
@@ -546,7 +546,11 @@ msgstr ""
546
546
  msgid "Unable to remove unmanaged path: %{path}"
547
547
  msgstr ""
548
548
 
549
- #: ../lib/r10k/util/setopts.rb:49
549
+ #: ../lib/r10k/util/setopts.rb:58
550
+ msgid "%{class_name} parameters '%{a}' and '%{b}' conflict. Specify one or the other, but not both"
551
+ msgstr ""
552
+
553
+ #: ../lib/r10k/util/setopts.rb:65
550
554
  msgid "%{class_name} cannot handle option '%{key}'"
551
555
  msgstr ""
552
556