r10k 3.3.3 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,9 +8,11 @@ describe 'r10k container' do
8
8
  include Pupperware::SpecHelpers
9
9
  def run_r10k(command)
10
10
  run_command("docker run --detach \
11
- --volume #{File.join(SPEC_DIRECTORY, 'fixtures')}:/test \
11
+ --volume #{File.join(SPEC_DIRECTORY, 'fixtures')}:/home/puppet/test \
12
12
  #{@image} #{command} \
13
- --puppetfile /test/Puppetfile")
13
+ --verbose \
14
+ --trace \
15
+ --puppetfile test/Puppetfile")
14
16
  end
15
17
 
16
18
  before(:all) do
@@ -35,7 +37,6 @@ describe 'r10k container' do
35
37
  container = result[:stdout].chomp
36
38
  wait_on_container_exit(container)
37
39
  expect(get_container_exit_code(container)).to eq(0)
38
- expect(Dir.exist?(File.join(SPEC_DIRECTORY, 'fixtures', 'modules', 'ntp'))).to eq(true)
39
40
  emit_log(container)
40
41
  teardown_container(container)
41
42
  end
@@ -1,2 +1,2 @@
1
- moduledir 'test/modules'
1
+ moduledir '/tmp/modules'
2
2
  mod 'puppetlabs/ntp'
@@ -140,14 +140,14 @@ module R10K
140
140
  end
141
141
 
142
142
  def visit_module(mod)
143
- logger.info _("Deploying Puppetfile content %{mod_path}") % {mod_path: mod.path}
143
+ logger.info _("Deploying %{origin} content %{path}") % {origin: mod.origin, path: mod.path}
144
144
  mod.sync(force: @force)
145
145
  end
146
146
 
147
147
  def write_environment_info!(environment, started_at, success)
148
148
  module_deploys = []
149
149
  begin
150
- environment.puppetfile.modules.each do |mod|
150
+ environment.modules.each do |mod|
151
151
  name = mod.name
152
152
  version = mod.version
153
153
  sha = mod.repo.head rescue nil
@@ -44,7 +44,7 @@ module R10K
44
44
 
45
45
  overrides = {}
46
46
  overrides[:cachedir] = @opts[:cachedir] unless @opts[:cachedir].nil?
47
- overrides[:deploy] = {} if @opts[:'puppet-path'] || @opts[:'generate-types']
47
+ overrides[:deploy] = {} if !@opts[:'puppet-path'].nil? || !@opts[:'generate-types'].nil?
48
48
  overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] unless @opts[:'puppet-path'].nil?
49
49
  overrides[:deploy][:generate_types] = @opts[:'generate-types'] unless @opts[:'generate-types'].nil?
50
50
 
@@ -1,6 +1,36 @@
1
1
  module R10K
2
2
  module Environment
3
+ def self.factory
4
+ @factory ||= R10K::KeyedFactory.new
5
+ end
6
+
7
+ def self.register(key, klass)
8
+ factory.register(key, klass)
9
+ end
10
+
11
+ def self.retrieve(key)
12
+ factory.retrieve(key)
13
+ end
14
+
15
+ def self.generate(type, name, basedir, dirname, options = {})
16
+ factory.generate(type, name, basedir, dirname, options)
17
+ end
18
+
19
+ def self.from_hash(name, hash)
20
+ R10K::Util::SymbolizeKeys.symbolize_keys!(hash)
21
+
22
+ basedir = hash.delete(:basedir)
23
+ dirname = hash.delete(:dirname) || name
24
+
25
+ type = hash.delete(:type)
26
+ type = type.is_a?(String) ? type.to_sym : type
27
+
28
+ generate(type, name, basedir, dirname, hash)
29
+ end
30
+
3
31
  require 'r10k/environment/base'
32
+ require 'r10k/environment/with_modules'
33
+ require 'r10k/environment/bare'
4
34
  require 'r10k/environment/git'
5
35
  require 'r10k/environment/svn'
6
36
  end
@@ -0,0 +1,16 @@
1
+ class R10K::Environment::Bare < R10K::Environment::WithModules
2
+
3
+ R10K::Environment.register(:bare, self)
4
+
5
+ def sync
6
+ path.mkpath
7
+ end
8
+
9
+ def status
10
+ :not_applicable
11
+ end
12
+
13
+ def signature
14
+ 'bare-default'
15
+ end
16
+ end
@@ -6,10 +6,14 @@ require 'forwardable'
6
6
  # This class implements an environment based on a Git branch.
7
7
  #
8
8
  # @since 1.3.0
9
- class R10K::Environment::Git < R10K::Environment::Base
9
+ class R10K::Environment::Git < R10K::Environment::WithModules
10
10
 
11
11
  include R10K::Logging
12
12
 
13
+ R10K::Environment.register(:git, self)
14
+ # Register git as the default environment type
15
+ R10K::Environment.register(nil, self)
16
+
13
17
  # @!attribute [r] remote
14
18
  # @return [String] The URL to the remote git repository
15
19
  attr_reader :remote
@@ -66,15 +70,12 @@ class R10K::Environment::Git < R10K::Environment::Base
66
70
 
67
71
  include R10K::Util::Purgeable
68
72
 
69
- def managed_directories
70
- [@full_path]
71
- end
72
-
73
73
  # Returns an array of the full paths to all the content being managed.
74
74
  # @note This implements a required method for the Purgeable mixin
75
75
  # @return [Array<String>]
76
76
  def desired_contents
77
77
  desired = [File.join(@full_path, '.git')]
78
78
  desired += @repo.tracked_paths.map { |entry| File.join(@full_path, entry) }
79
+ desired += super
79
80
  end
80
81
  end
@@ -9,6 +9,8 @@ class R10K::Environment::SVN < R10K::Environment::Base
9
9
 
10
10
  include R10K::Logging
11
11
 
12
+ R10K::Environment.register(:svn, self)
13
+
12
14
  # @!attribute [r] remote
13
15
  # @return [String] The URL to the remote SVN branch to check out
14
16
  attr_reader :remote
@@ -0,0 +1,139 @@
1
+ require 'r10k/logging'
2
+ require 'r10k/util/purgeable'
3
+
4
+ # This abstract base class implements an environment that can include module
5
+ # content
6
+ #
7
+ # @since 3.4.0
8
+ class R10K::Environment::WithModules < R10K::Environment::Base
9
+
10
+ include R10K::Logging
11
+
12
+ # @!attribute [r] moduledir
13
+ # @return [String] The directory to install environment-defined modules
14
+ # into (default: #{basedir}/modules)
15
+ attr_reader :moduledir
16
+
17
+ # Initialize the given environment.
18
+ #
19
+ # @param name [String] The unique name describing this environment.
20
+ # @param basedir [String] The base directory where this environment will be created.
21
+ # @param dirname [String] The directory name for this environment.
22
+ # @param options [Hash] An additional set of options for this environment.
23
+ #
24
+ # @param options [String] :moduledir The path to install modules to
25
+ # @param options [Hash] :modules Modules to add to the environment
26
+ def initialize(name, basedir, dirname, options = {})
27
+ super(name, basedir, dirname, options)
28
+
29
+ @managed_content = {}
30
+ @modules = []
31
+ @moduledir = case options[:moduledir]
32
+ when nil
33
+ File.join(@basedir, @dirname, 'modules')
34
+ when File.absolute_path(options[:moduledir])
35
+ options.delete(:moduledir)
36
+ else
37
+ File.join(@basedir, @dirname, options.delete(:moduledir))
38
+ end
39
+
40
+ modhash = options.delete(:modules)
41
+ load_modules(modhash) unless modhash.nil?
42
+ end
43
+
44
+ # @return [Array<R10K::Module::Base>] All modules associated with this environment.
45
+ # Modules may originate from either:
46
+ # - The r10k environment object
47
+ # - A Puppetfile in the environment's content
48
+ def modules
49
+ return @modules if @puppetfile.nil?
50
+
51
+ @puppetfile.load unless @puppetfile.loaded?
52
+ @modules + @puppetfile.modules
53
+ end
54
+
55
+ def accept(visitor)
56
+ visitor.visit(:environment, self) do
57
+ @modules.each do |mod|
58
+ mod.accept(visitor)
59
+ end
60
+
61
+ puppetfile.accept(visitor)
62
+ validate_no_module_conflicts
63
+ end
64
+ end
65
+
66
+ def load_modules(module_hash)
67
+ module_hash.each do |name, args|
68
+ add_module(name, args)
69
+ end
70
+ end
71
+
72
+ # @param [String] name
73
+ # @param [*Object] args
74
+ def add_module(name, args)
75
+ if args.is_a?(Hash)
76
+ # symbolize keys in the args hash
77
+ args = args.inject({}) { |memo,(k,v)| memo[k.to_sym] = v; memo }
78
+ end
79
+
80
+ if args.is_a?(Hash) && install_path = args.delete(:install_path)
81
+ install_path = resolve_install_path(install_path)
82
+ validate_install_path(install_path, name)
83
+ else
84
+ install_path = @moduledir
85
+ end
86
+
87
+ # Keep track of all the content this environment is managing to enable purging.
88
+ @managed_content[install_path] = Array.new unless @managed_content.has_key?(install_path)
89
+
90
+ mod = R10K::Module.new(name, install_path, args, self.name)
91
+ mod.origin = 'Environment'
92
+
93
+ @managed_content[install_path] << mod.name
94
+ @modules << mod
95
+ end
96
+
97
+ def validate_no_module_conflicts
98
+ @puppetfile.load unless @puppetfile.loaded?
99
+ conflicts = (@modules + @puppetfile.modules)
100
+ .group_by { |mod| mod.name }
101
+ .select { |_, v| v.size > 1 }
102
+ .map(&:first)
103
+ unless conflicts.empty?
104
+ msg = _('Puppetfile cannot contain module names defined by environment %{name}') % {name: self.name}
105
+ msg += ' '
106
+ msg += _("Remove the conflicting definitions of the following modules: %{conflicts}" % { conflicts: conflicts.join(' ') })
107
+ raise R10K::Error.new(msg)
108
+ end
109
+ end
110
+
111
+ include R10K::Util::Purgeable
112
+
113
+ # Returns an array of the full paths that can be purged.
114
+ # @note This implements a required method for the Purgeable mixin
115
+ # @return [Array<String>]
116
+ def managed_directories
117
+ [@full_path]
118
+ end
119
+
120
+ # Returns an array of the full paths of filenames that should exist. Files
121
+ # inside managed_directories that are not listed in desired_contents will
122
+ # be purged.
123
+ # @note This implements a required method for the Purgeable mixin
124
+ # @return [Array<String>]
125
+ def desired_contents
126
+ list = @managed_content.keys
127
+ list += @managed_content.flat_map do |install_path, modnames|
128
+ modnames.collect { |name| File.join(install_path, name) }
129
+ end
130
+ end
131
+
132
+ def purge_exclusions
133
+ super + @managed_content.flat_map do |install_path, modnames|
134
+ modnames.map do |name|
135
+ File.join(install_path, name, '**', '*')
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,4 +1,4 @@
1
- require 'colored'
1
+ require 'colored2'
2
2
  require 'r10k/logging'
3
3
  require 'log4r/outputter/iooutputter'
4
4
 
@@ -31,6 +31,10 @@ class R10K::Module::Base
31
31
  # @return [R10K::Environment, nil] The parent environment of the module
32
32
  attr_reader :environment
33
33
 
34
+ # @!attribute [rw] origin
35
+ # @return [String] Where the module was sourced from. E.g., "Puppetfile"
36
+ attr_accessor :origin
37
+
34
38
  # There's been some churn over `author` vs `owner` and `full_name` over
35
39
  # `title`, so in the short run it's easier to support both and deprecate one
36
40
  # later.
@@ -47,6 +51,7 @@ class R10K::Module::Base
47
51
  @owner, @name = parse_title(@title)
48
52
  @path = Pathname.new(File.join(@dirname, @name))
49
53
  @environment = environment
54
+ @origin = 'external' # Expect Puppetfile or R10k::Environment to set this to a specific value
50
55
  end
51
56
 
52
57
  # @deprecated
@@ -75,7 +75,11 @@ class R10K::Module::Forge < R10K::Module::Base
75
75
 
76
76
  # @return [String] The version of the currently installed module
77
77
  def current_version
78
- @metadata ? @metadata.version : nil
78
+ if insync?
79
+ (@metadata ||= @metadata_file.read).nil? ? nil : @metadata.version
80
+ else
81
+ nil
82
+ end
79
83
  end
80
84
 
81
85
  alias version current_version
@@ -64,6 +64,7 @@ class Puppetfile
64
64
  end
65
65
 
66
66
  def load(default_branch_override = nil)
67
+ return true if self.loaded?
67
68
  if File.readable? @puppetfile_path
68
69
  self.load!(default_branch_override)
69
70
  else
@@ -83,6 +84,10 @@ class Puppetfile
83
84
  raise R10K::Error.wrap(e, _("Failed to evaluate %{path}") % {path: @puppetfile_path})
84
85
  end
85
86
 
87
+ def loaded?
88
+ @loaded
89
+ end
90
+
86
91
  # @param [Array<String>] modules
87
92
  def validate_no_duplicate_names(modules)
88
93
  dupes = modules
@@ -129,6 +134,7 @@ class Puppetfile
129
134
  @managed_content[install_path] = Array.new unless @managed_content.has_key?(install_path)
130
135
 
131
136
  mod = R10K::Module.new(name, install_path, args, @environment)
137
+ mod.origin = 'Puppetfile'
132
138
 
133
139
  @managed_content[install_path] << mod.name
134
140
  @modules << mod
@@ -32,7 +32,10 @@ module R10K
32
32
  end
33
33
 
34
34
  require 'r10k/source/base'
35
+ require 'r10k/source/hash'
35
36
  require 'r10k/source/git'
36
37
  require 'r10k/source/svn'
38
+ require 'r10k/source/yaml'
39
+ require 'r10k/source/yamldir'
37
40
  end
38
41
  end
@@ -0,0 +1,158 @@
1
+ # This class implements an environment source based on recieving a hash of
2
+ # environments
3
+ #
4
+ # @since 3.4.0
5
+ #
6
+ # DESCRIPTION
7
+ #
8
+ # This class implements environments defined by a hash having the following
9
+ # schema:
10
+ #
11
+ # ---
12
+ # type: object
13
+ # additionalProperties:
14
+ # type: object
15
+ # properties:
16
+ # type:
17
+ # type: string
18
+ # basedir:
19
+ # type: string
20
+ # modules:
21
+ # type: object
22
+ # additionalProperties:
23
+ # type: object
24
+ # moduledir:
25
+ # type: string
26
+ # additionalProperties:
27
+ # type: string
28
+ #
29
+ # The top-level keys in the hash are environment names. Keys in individual
30
+ # environments should be the same as those which would be given to define a
31
+ # single source in r10k.yaml. Additionally, the "modules" key (and moduledir)
32
+ # can be used to designate module content for the environment, independent of
33
+ # the base source parameters.
34
+ #
35
+ # Example:
36
+ #
37
+ # ---
38
+ # production:
39
+ # type: git
40
+ # remote: 'https://github.com/reidmv/control-repo.git'
41
+ # ref: '1.0.0'
42
+ # modules:
43
+ # geoffwilliams-r_profile: '1.1.0'
44
+ # geoffwilliams-r_role: '2.0.0'
45
+ #
46
+ # development:
47
+ # type: git
48
+ # remote: 'https://github.com/reidmv/control-repo.git'
49
+ # ref: 'master'
50
+ # modules:
51
+ # geoffwilliams-r_profile: '1.1.0'
52
+ # geoffwilliams-r_role: '2.0.0'
53
+ #
54
+ # USAGE
55
+ #
56
+ # The following is an example implementation class showing how to use the
57
+ # R10K::Source::Hash abstract base class. Assume an r10k.yaml file such as:
58
+ #
59
+ # ---
60
+ # sources:
61
+ # proof-of-concept:
62
+ # type: demo
63
+ # basedir: '/etc/puppetlabs/code/environments'
64
+ #
65
+ # Class implementation:
66
+ #
67
+ # class R10K::Source::Demo < R10K::Source::Hash
68
+ # R10K::Source.register(:demo, self)
69
+ #
70
+ # def initialize(name, basedir, options = {})
71
+ # # This is just a demo class, so we hard-code an example :environments
72
+ # # hash here. In a real class, we might do something here such as
73
+ # # perform an API call to retrieve an :environments hash.
74
+ # options[:environments] = {
75
+ # 'production' => {
76
+ # 'remote' => 'https://git.example.com/puppet/control-repo.git',
77
+ # 'ref' => 'release-141',
78
+ # 'modules' => {
79
+ # 'puppetlabs-stdlib' => '6.1.0',
80
+ # 'puppetlabs-ntp' => '8.1.0',
81
+ # 'example-myapp1' => {
82
+ # 'git' => 'https://git.example.com/puppet/example-myapp1.git',
83
+ # 'ref' => 'v1.3.0',
84
+ # },
85
+ # },
86
+ # },
87
+ # 'development' => {
88
+ # 'remote' => 'https://git.example.com/puppet/control-repo.git',
89
+ # 'ref' => 'master',
90
+ # 'modules' => {
91
+ # 'puppetlabs-stdlib' => '6.1.0',
92
+ # 'puppetlabs-ntp' => '8.1.0',
93
+ # 'example-myapp1' => {
94
+ # 'git' => 'https://git.example.com/puppet/example-myapp1.git',
95
+ # 'ref' => 'v1.3.1',
96
+ # },
97
+ # },
98
+ # },
99
+ # }
100
+ #
101
+ # # All we need to do is supply options with the :environments hash.
102
+ # # The R10K::Source::Hash parent class takes care of the rest.
103
+ # super(name, basedir, options)
104
+ # end
105
+ # end
106
+ #
107
+ # Example output:
108
+ #
109
+ # [root@master:~] % r10k deploy environment production -pv
110
+ # INFO -> Using Puppetfile '/etc/puppetlabs/code/environments/production/Puppetfile'
111
+ # INFO -> Using Puppetfile '/etc/puppetlabs/code/environments/development/Puppetfile'
112
+ # INFO -> Deploying environment /etc/puppetlabs/code/environments/production
113
+ # INFO -> Environment production is now at 74ea2e05bba796918e4ff1803018c526337ef5f3
114
+ # INFO -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/stdlib
115
+ # INFO -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/ntp
116
+ # INFO -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/myapp1
117
+ # INFO -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/ruby_task_helper
118
+ # INFO -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/bolt_shim
119
+ # INFO -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/apply_helpers
120
+ #
121
+ class R10K::Source::Hash < R10K::Source::Base
122
+
123
+ # @param name [String] The identifier for this source.
124
+ # @param basedir [String] The base directory where the generated environments will be created.
125
+ # @param options [Hash] An additional set of options for this source. The
126
+ # semantics of this hash may depend on the source implementation.
127
+ #
128
+ # @option options [Boolean, String] :prefix If a String this becomes the prefix.
129
+ # If true, will use the source name as the prefix. All sources should respect this option.
130
+ # Defaults to false for no environment prefix.
131
+ # @option options [Hash] :environments The hash definition of environments
132
+ def initialize(name, basedir, options = {})
133
+ super(name, basedir, options)
134
+
135
+ @environments_hash = options.delete(:environments) || {}
136
+
137
+ @environments_hash.keys.each do |name|
138
+ # TODO: deal with names that aren't valid
139
+ ::R10K::Util::SymbolizeKeys.symbolize_keys!(@environments_hash[name])
140
+ @environments_hash[name][:basedir] = basedir
141
+ @environments_hash[name][:dirname] = name
142
+ end
143
+ end
144
+
145
+ def environments
146
+ @environments ||= @environments_hash.map do |name, hash|
147
+ R10K::Environment.from_hash(name, hash)
148
+ end
149
+ end
150
+
151
+ # List all environments that should exist in the basedir for this source
152
+ # @note This is required by {R10K::Util::Basedir}
153
+ # @return [Array<String>]
154
+ def desired_contents
155
+ environments.map {|env| env.dirname }
156
+ end
157
+
158
+ end