r10k 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkd +25 -0
  3. data/Gemfile +1 -1
  4. data/doc/dynamic-environments/configuration.mkd +36 -11
  5. data/integration/tests/basic_functionality/proxy_with_pe_only_module.rb +128 -0
  6. data/lib/r10k/errors.rb +18 -0
  7. data/lib/r10k/forge/module_release.rb +3 -3
  8. data/lib/r10k/git.rb +53 -0
  9. data/lib/r10k/git/rugged/bare_repository.rb +16 -7
  10. data/lib/r10k/git/rugged/base_repository.rb +16 -2
  11. data/lib/r10k/git/rugged/credentials.rb +9 -0
  12. data/lib/r10k/git/rugged/working_repository.rb +18 -5
  13. data/lib/r10k/git/shellgit/bare_repository.rb +12 -3
  14. data/lib/r10k/git/shellgit/base_repository.rb +19 -3
  15. data/lib/r10k/git/shellgit/working_repository.rb +13 -3
  16. data/lib/r10k/initializers.rb +1 -0
  17. data/lib/r10k/settings.rb +41 -22
  18. data/lib/r10k/settings/collection.rb +25 -21
  19. data/lib/r10k/settings/definition.rb +14 -2
  20. data/lib/r10k/settings/helpers.rb +38 -0
  21. data/lib/r10k/settings/list.rb +107 -0
  22. data/lib/r10k/util/symbolize_keys.rb +6 -2
  23. data/lib/r10k/version.rb +1 -1
  24. data/r10k.gemspec +1 -1
  25. data/r10k.yaml.example +38 -12
  26. data/spec/shared-examples/git/bare_repository.rb +62 -0
  27. data/spec/shared-examples/git/working_repository.rb +57 -11
  28. data/spec/shared-examples/settings/ancestry.rb +44 -0
  29. data/spec/unit/forge/module_release_spec.rb +1 -1
  30. data/spec/unit/git/rugged/credentials_spec.rb +6 -0
  31. data/spec/unit/settings/collection_spec.rb +2 -1
  32. data/spec/unit/settings/definition_spec.rb +6 -5
  33. data/spec/unit/settings/inheritance_spec.rb +38 -0
  34. data/spec/unit/settings/list_spec.rb +88 -0
  35. data/spec/unit/settings_spec.rb +52 -23
  36. data/spec/unit/util/attempt_spec.rb +1 -1
  37. data/spec/unit/util/symbolize_keys_spec.rb +25 -9
  38. metadata +11 -4
@@ -36,7 +36,12 @@ class R10K::Git::Rugged::WorkingRepository < R10K::Git::Rugged::BaseRepository
36
36
  # alternate object database.
37
37
  options = {:credentials => credentials}
38
38
  options.merge!(:alternates => [File.join(opts[:reference], 'objects')]) if opts[:reference]
39
- @_rugged_repo = ::Rugged::Repository.clone_at(remote, @path.to_s, options)
39
+
40
+ proxy = R10K::Git.get_proxy_for_remote(remote)
41
+
42
+ R10K::Git.with_proxy(proxy) do
43
+ @_rugged_repo = ::Rugged::Repository.clone_at(remote, @path.to_s, options)
44
+ end
40
45
 
41
46
  if opts[:reference]
42
47
  alternates << File.join(opts[:reference], 'objects')
@@ -70,11 +75,19 @@ class R10K::Git::Rugged::WorkingRepository < R10K::Git::Rugged::BaseRepository
70
75
  end
71
76
  end
72
77
 
73
- def fetch(remote = 'origin')
74
- logger.debug1 { "Fetching remote '#{remote}' at #{@path}" }
78
+ def fetch(remote_name = 'origin')
79
+ logger.debug1 { "Fetching remote '#{remote_name}' at #{@path}" }
75
80
  options = {:credentials => credentials}
76
- refspecs = ["+refs/heads/*:refs/remotes/#{remote}/*"]
77
- results = with_repo { |repo| repo.fetch(remote, refspecs, options) }
81
+ refspecs = ["+refs/heads/*:refs/remotes/#{remote_name}/*"]
82
+
83
+ remote = remotes[remote_name]
84
+ proxy = R10K::Git.get_proxy_for_remote(remote)
85
+ results = nil
86
+
87
+ R10K::Git.with_proxy(proxy) do
88
+ results = with_repo { |repo| repo.fetch(remote_name, refspecs, options) }
89
+ end
90
+
78
91
  report_transfer(results, remote)
79
92
  rescue Rugged::SshError, Rugged::NetworkError => e
80
93
  raise R10K::Git::GitError.new(e.message, :git_dir => git_dir, :backtrace => e.backtrace)
@@ -21,11 +21,20 @@ class R10K::Git::ShellGit::BareRepository < R10K::Git::ShellGit::BaseRepository
21
21
  end
22
22
 
23
23
  def clone(remote)
24
- git ['clone', '--mirror', remote, git_dir.to_s]
24
+ proxy = R10K::Git.get_proxy_for_remote(remote)
25
+
26
+ R10K::Git.with_proxy(proxy) do
27
+ git ['clone', '--mirror', remote, git_dir.to_s]
28
+ end
25
29
  end
26
30
 
27
- def fetch
28
- git ['fetch', '--prune'], :git_dir => git_dir.to_s
31
+ def fetch(remote_name='origin')
32
+ remote = remotes[remote_name]
33
+ proxy = R10K::Git.get_proxy_for_remote(remote)
34
+
35
+ R10K::Git.with_proxy(proxy) do
36
+ git ['fetch', remote_name, '--prune'], :git_dir => git_dir.to_s
37
+ end
29
38
  end
30
39
 
31
40
  def exist?
@@ -1,4 +1,5 @@
1
1
  require 'r10k/git/shellgit'
2
+ require 'r10k/util/subprocess'
2
3
  require 'r10k/logging'
3
4
 
4
5
  class R10K::Git::ShellGit::BaseRepository
@@ -47,6 +48,23 @@ class R10K::Git::ShellGit::BaseRepository
47
48
  end
48
49
  end
49
50
 
51
+ # @return [Hash] Collection of remotes for this repo, keys are the remote name and values are the remote URL.
52
+ def remotes
53
+ result = git ['config', '--local', '--get-regexp', '^remote\..*\.url$'], :git_dir => git_dir.to_s, :raise_on_fail => false
54
+
55
+ if result.success?
56
+ Hash[
57
+ result.stdout.split("\n").collect do |remote|
58
+ matches = /^remote\.(.*)\.url (.*)$/.match(remote)
59
+
60
+ [matches[1], matches[2]]
61
+ end
62
+ ]
63
+ else
64
+ {}
65
+ end
66
+ end
67
+
50
68
  include R10K::Logging
51
69
 
52
70
  private
@@ -95,8 +113,6 @@ class R10K::Git::ShellGit::BaseRepository
95
113
  subproc.raise_on_fail = raise_on_fail
96
114
  subproc.logger = self.logger
97
115
 
98
- result = subproc.execute
99
-
100
- result
116
+ subproc.execute
101
117
  end
102
118
  end
@@ -32,7 +32,12 @@ class R10K::Git::ShellGit::WorkingRepository < R10K::Git::ShellGit::BaseReposito
32
32
  if opts[:reference]
33
33
  argv += ['--reference', opts[:reference]]
34
34
  end
35
- git argv
35
+
36
+ proxy = R10K::Git.get_proxy_for_remote(remote)
37
+
38
+ R10K::Git.with_proxy(proxy) do
39
+ git argv
40
+ end
36
41
 
37
42
  if opts[:ref]
38
43
  checkout(opts[:ref])
@@ -46,8 +51,13 @@ class R10K::Git::ShellGit::WorkingRepository < R10K::Git::ShellGit::BaseReposito
46
51
  git ['checkout', ref], :path => @path.to_s
47
52
  end
48
53
 
49
- def fetch
50
- git ['fetch'], :path => @path.to_s
54
+ def fetch(remote_name='origin')
55
+ remote = remotes[remote_name]
56
+ proxy = R10K::Git.get_proxy_for_remote(remote)
57
+
58
+ R10K::Git.with_proxy(proxy) do
59
+ git ['fetch', remote_name, '--prune'], :path => @path.to_s
60
+ end
51
61
  end
52
62
 
53
63
  def exist?
@@ -42,6 +42,7 @@ module R10K
42
42
  with_setting(:provider) { |value| R10K::Git.provider = value }
43
43
  with_setting(:username) { |value| R10K::Git.settings[:username] = value }
44
44
  with_setting(:private_key) { |value| R10K::Git.settings[:private_key] = value }
45
+ with_setting(:proxy) { |value| R10K::Git.settings[:proxy] = value }
45
46
  with_setting(:repositories) { |value| R10K::Git.settings[:repositories] = value }
46
47
  end
47
48
  end
@@ -7,6 +7,7 @@ module R10K
7
7
 
8
8
  require 'r10k/settings/collection'
9
9
  require 'r10k/settings/definition'
10
+ require 'r10k/settings/list'
10
11
 
11
12
  def self.git_settings
12
13
  R10K::Settings::Collection.new(:git, [
@@ -29,20 +30,33 @@ module R10K
29
30
  Only used by the 'rugged' Git provider.",
30
31
  }),
31
32
 
32
- Definition.new(:repositories, {
33
- :desc => "Repository specific configuration.",
34
- :default => [],
35
- :normalize => lambda do |repositories|
36
- # The config file loading logic recursively converts hash keys that are strings to symbols,
37
- # It doesn't understand hashes inside arrays though so we have to do this manually.
38
- repositories.map do |repo|
39
- repo.inject({}) do |retval, (key, value)|
40
- retval[key.to_sym] = value
41
- retval
42
- end
43
- end
44
- end
45
- }),
33
+ URIDefinition.new(:proxy, {
34
+ :desc => "An optional proxy server to use when interacting with Git sources via HTTP(S).",
35
+ :default => :inherit,
36
+ }),
37
+
38
+ List.new(:repositories, lambda {
39
+ R10K::Settings::Collection.new(nil, [
40
+ Definition.new(:remote, {
41
+ :desc => "Remote source that repository-specific settings should apply to.",
42
+ }),
43
+
44
+ Definition.new(:private_key, {
45
+ :desc => "The path to the SSH private key for Git SSH remotes.
46
+ Only used by the 'rugged' Git provider.",
47
+ :default => :inherit,
48
+ }),
49
+
50
+ URIDefinition.new(:proxy, {
51
+ :desc => "An optional proxy server to use when interacting with Git sources via HTTP(S).",
52
+ :default => :inherit,
53
+ }),
54
+ ])
55
+ },
56
+ {
57
+ :desc => "Repository specific configuration.",
58
+ :default => [],
59
+ }),
46
60
  ])
47
61
  end
48
62
 
@@ -50,14 +64,7 @@ module R10K
50
64
  R10K::Settings::Collection.new(:forge, [
51
65
  URIDefinition.new(:proxy, {
52
66
  :desc => "An optional proxy server to use when downloading modules from the forge.",
53
- :default => lambda do
54
- [
55
- ENV['HTTPS_PROXY'],
56
- ENV['https_proxy'],
57
- ENV['HTTP_PROXY'],
58
- ENV['http_proxy']
59
- ].find { |value| value }
60
- end
67
+ :default => :inherit,
61
68
  }),
62
69
 
63
70
  URIDefinition.new(:baseurl, {
@@ -104,6 +111,18 @@ module R10K
104
111
  end
105
112
  }),
106
113
 
114
+ URIDefinition.new(:proxy, {
115
+ :desc => "Proxy to use for all r10k operations which occur over HTTP(S).",
116
+ :default => lambda {
117
+ [
118
+ ENV['HTTPS_PROXY'],
119
+ ENV['https_proxy'],
120
+ ENV['HTTP_PROXY'],
121
+ ENV['http_proxy']
122
+ ].find { |value| value }
123
+ },
124
+ }),
125
+
107
126
  R10K::Settings.forge_settings,
108
127
 
109
128
  R10K::Settings.git_settings,
@@ -1,3 +1,4 @@
1
+ require 'r10k/settings/helpers'
1
2
  require 'r10k/settings/definition'
2
3
  require 'r10k/util/setopts'
3
4
  require 'r10k/util/symbolize_keys'
@@ -9,6 +10,7 @@ module R10K
9
10
  # Define a group of settings, which can be single definitions or nested
10
11
  # collections.
11
12
  class Collection
13
+ include R10K::Settings::Helpers
12
14
 
13
15
  # @!attribute [r] name
14
16
  # @return [String] The name of this collection
@@ -18,7 +20,14 @@ module R10K
18
20
  # @param settings [Array] All settings in this collection
19
21
  def initialize(name, settings)
20
22
  @name = name
21
- @settings = Hash[settings.map { |s| [s.name, s] }]
23
+
24
+ @settings = {}
25
+
26
+ # iterate through settings and adopt them
27
+ settings.each do |s|
28
+ s.parent = self
29
+ @settings[s.name] = s
30
+ end
22
31
  end
23
32
 
24
33
  # Assign new values, perform validation checks, and return the final
@@ -39,8 +48,8 @@ module R10K
39
48
  def assign(newvalues)
40
49
  return if newvalues.nil?
41
50
 
42
- # TODO: this results in inconsistency for hashes inside arrays.
43
51
  R10K::Util::SymbolizeKeys.symbolize_keys!(newvalues)
52
+
44
53
  @settings.each_pair do |name, setting|
45
54
  if newvalues.key?(name)
46
55
  setting.assign(newvalues[name])
@@ -64,7 +73,13 @@ module R10K
64
73
  end
65
74
 
66
75
  if !errors.empty?
67
- raise ValidationError.new("Validation failed for #{@name} settings group", :errors => errors)
76
+ if @name
77
+ msg = "Validation failed for '#{@name}' settings group"
78
+ else
79
+ msg = "Validation failed for settings group"
80
+ end
81
+
82
+ raise ValidationError.new(msg, :errors => errors)
68
83
  end
69
84
  end
70
85
 
@@ -72,12 +87,19 @@ module R10K
72
87
  # @return [Hash]
73
88
  def resolve
74
89
  rv = {}
90
+
75
91
  @settings.each_pair do |name, setting|
76
92
  rv[name] = setting.resolve
77
93
  end
94
+
78
95
  rv.freeze
79
96
  end
80
97
 
98
+ # Access individual settings via a Hash-like interface.
99
+ def [](name)
100
+ @settings[name]
101
+ end
102
+
81
103
  class ValidationError < R10K::Error
82
104
 
83
105
  attr_reader :errors
@@ -95,24 +117,6 @@ module R10K
95
117
  end
96
118
  struct.join("\n")
97
119
  end
98
-
99
- private
100
-
101
- def structure_exception(name, exc)
102
- struct = []
103
- struct << "#{name}:"
104
- if exc.is_a? ValidationError
105
- struct << indent(exc.format)
106
- else
107
- struct << indent(exc.message)
108
- end
109
- struct.join("\n")
110
- end
111
-
112
- def indent(str, level = 4)
113
- prefix = ' ' * level
114
- str.gsub(/^/, prefix)
115
- end
116
120
  end
117
121
  end
118
122
  end
@@ -1,3 +1,4 @@
1
+ require 'r10k/settings/helpers'
1
2
  require 'r10k/util/setopts'
2
3
 
3
4
  module R10K
@@ -6,10 +7,10 @@ module R10K
6
7
  # Define a single setting and additional attributes like descriptions,
7
8
  # default values, and validation.
8
9
  class Definition
9
-
10
10
  require 'r10k/settings/uri_definition'
11
11
  require 'r10k/settings/enum_definition'
12
12
 
13
+ include R10K::Settings::Helpers
13
14
  include R10K::Util::Setopts
14
15
 
15
16
  # @!attribute [r] name
@@ -90,7 +91,18 @@ module R10K
90
91
  if !@value.nil?
91
92
  @value
92
93
  elsif @default
93
- @default.is_a?(Proc) ? @default.call : @default
94
+ if @default == :inherit
95
+ # walk all the way up to root, starting with grandparent
96
+ ancestor = parent
97
+
98
+ while ancestor = ancestor.parent
99
+ return ancestor[@name].resolve if ancestor.respond_to?(:[]) && ancestor[@name]
100
+ end
101
+ elsif @default.is_a?(Proc)
102
+ @default.call
103
+ else
104
+ @default
105
+ end
94
106
  end
95
107
  end
96
108
 
@@ -0,0 +1,38 @@
1
+ require 'r10k/errors'
2
+ require 'r10k/settings/collection'
3
+
4
+ module R10K
5
+ module Settings
6
+ module Helpers
7
+ def self.included(klass)
8
+ klass.send(:include, InstanceMethods)
9
+ klass.send(:extend, ClassMethods)
10
+ end
11
+
12
+ module InstanceMethods
13
+ # Assign a parent collection to this setting. Parent may only be
14
+ # assigned once.
15
+ #
16
+ # @param new_parent [R10K::Settings::Collection] Parent collection
17
+ def parent=(new_parent)
18
+ unless @parent.nil?
19
+ raise R10K::Error.new("#{self.class} instances cannot be reassigned to a new parent.")
20
+ end
21
+
22
+ unless new_parent.is_a?(R10K::Settings::Collection) || new_parent.is_a?(R10K::Settings::List)
23
+ raise R10K::Error.new("#{self.class} instances may only belong to a settings collection or list.")
24
+ end
25
+
26
+ @parent = new_parent
27
+ end
28
+
29
+ def parent
30
+ @parent
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,107 @@
1
+ require 'r10k/settings/helpers'
2
+ require 'r10k/settings/collection'
3
+ require 'r10k/errors'
4
+ require 'r10k/util/setopts'
5
+
6
+ module R10K
7
+ module Settings
8
+
9
+ # A container for an arbitrarily long list of other settings.
10
+ class List
11
+ include R10K::Settings::Helpers
12
+ include R10K::Util::Setopts
13
+
14
+ # @!attribute [r] name
15
+ # @return [String] The name of this collection
16
+ attr_reader :name
17
+
18
+ # @param name [Symbol] The name of the setting for this definition.
19
+ # @param item_proc [#call] An object whose #call method will return a
20
+ # new instance of another R10K::Settings class to hold each item
21
+ # added to this list.
22
+ # @param opts [Hash] Additional options for this definition to control
23
+ # validation, normalization, and the like.
24
+ #
25
+ # @options opts [String] :desc Extended description of this setting.
26
+ # @options opts [Array] :default Initial/default contents of the list.
27
+ def initialize(name, item_proc, opts = {})
28
+ @name = name
29
+ @item_proc = item_proc
30
+ @items = []
31
+
32
+ setopts(opts, allowed_initialize_opts)
33
+ end
34
+
35
+ # Takes an array of key/value pairs and assigns each into a
36
+ # new instance created by invoking @item_proc.
37
+ #
38
+ # @param items [Array] List of items to add to this list.
39
+ def assign(items)
40
+ return if items.nil?
41
+
42
+ items.each do |values|
43
+ new_item = @item_proc.call
44
+ new_item.parent = self
45
+ new_item.assign(values)
46
+ @items << new_item
47
+ end
48
+ end
49
+
50
+ # Validate all items in the list and return validation errors
51
+ #
52
+ # @return [nil, Hash] If all validation passed nil will be returned; if
53
+ # validation failed then a hash of those errors will be returned.
54
+ def validate
55
+ errors = {}
56
+
57
+ @items.each_with_index do |item, idx|
58
+ begin
59
+ item.validate
60
+ rescue => error
61
+ errors[idx+1] = error
62
+ end
63
+ end
64
+
65
+ if !errors.empty?
66
+ raise ValidationError.new("Validation failed for '#{@name}' settings list", :errors => errors)
67
+ end
68
+ end
69
+
70
+ # Evaluate all items in the list and return a frozen array of the final values.
71
+ # @return [Array]
72
+ def resolve
73
+ @items.collect { |item| item.resolve }.freeze
74
+ end
75
+
76
+ class ValidationError < R10K::Error
77
+ attr_reader :errors
78
+
79
+ def initialize(mesg, options = {})
80
+ super
81
+ @errors = options[:errors]
82
+ end
83
+
84
+ def format
85
+ struct = []
86
+ struct << "#{message}:"
87
+ @errors.each do |item, error|
88
+ struct << indent(structure_exception("Item #{item}", error))
89
+ end
90
+ struct.join("\n")
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ # Subclasses may define additional params that are accepted at
97
+ # initialization; they should override this method to add any
98
+ # additional fields that should be respected.
99
+ def allowed_initialize_opts
100
+ {
101
+ :desc => true,
102
+ :default => true,
103
+ }
104
+ end
105
+ end
106
+ end
107
+ end