r10k 3.3.3 → 3.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/.github/pull_request_template.md +1 -0
  3. data/.github/workflows/docker.yml +56 -0
  4. data/.github/workflows/release.yml +36 -0
  5. data/.travis.yml +23 -8
  6. data/CHANGELOG.mkd +57 -4
  7. data/CODEOWNERS +1 -0
  8. data/Gemfile +1 -1
  9. data/README.mkd +4 -3
  10. data/azure-pipelines.yml +2 -1
  11. data/bin/r10k +1 -1
  12. data/doc/dynamic-environments/configuration.mkd +160 -2
  13. data/doc/dynamic-environments/git-environments.mkd +1 -1
  14. data/doc/dynamic-environments/master-configuration.mkd +28 -58
  15. data/doc/faq.mkd +6 -1
  16. data/doc/puppetfile.mkd +2 -0
  17. data/docker/Makefile +39 -17
  18. data/docker/r10k/Dockerfile +36 -15
  19. data/docker/r10k/adduser.sh +13 -0
  20. data/docker/r10k/docker-entrypoint.d/10-analytics.sh +1 -1
  21. data/docker/r10k/release.Dockerfile +54 -0
  22. data/docker/spec/dockerfile_spec.rb +4 -3
  23. data/docker/spec/fixtures/Puppetfile +1 -1
  24. data/integration/Rakefile +2 -2
  25. data/lib/r10k/action/deploy/environment.rb +7 -3
  26. data/lib/r10k/action/deploy/module.rb +5 -1
  27. data/lib/r10k/action/runner.rb +4 -4
  28. data/lib/r10k/cli/deploy.rb +1 -1
  29. data/lib/r10k/environment.rb +30 -0
  30. data/lib/r10k/environment/bare.rb +16 -0
  31. data/lib/r10k/environment/git.rb +6 -5
  32. data/lib/r10k/environment/svn.rb +2 -0
  33. data/lib/r10k/environment/with_modules.rb +139 -0
  34. data/lib/r10k/forge/module_release.rb +2 -2
  35. data/lib/r10k/logging/terminaloutputter.rb +1 -1
  36. data/lib/r10k/module/base.rb +5 -0
  37. data/lib/r10k/module/forge.rb +5 -1
  38. data/lib/r10k/puppetfile.rb +6 -0
  39. data/lib/r10k/source.rb +4 -0
  40. data/lib/r10k/source/exec.rb +51 -0
  41. data/lib/r10k/source/hash.rb +182 -0
  42. data/lib/r10k/source/yaml.rb +20 -0
  43. data/lib/r10k/source/yamldir.rb +32 -0
  44. data/lib/r10k/util/attempt.rb +1 -1
  45. data/lib/r10k/version.rb +4 -1
  46. data/locales/r10k.pot +65 -22
  47. data/r10k.gemspec +6 -2
  48. data/spec/unit/action/deploy/environment_spec.rb +1 -0
  49. data/spec/unit/action/deploy/module_spec.rb +13 -0
  50. data/spec/unit/action/puppetfile/install_spec.rb +3 -1
  51. data/spec/unit/action/runner_spec.rb +2 -2
  52. data/spec/unit/forge/module_release_spec.rb +14 -10
  53. data/spec/unit/source/exec_spec.rb +81 -0
  54. data/spec/unit/source/hash_spec.rb +54 -0
  55. data/spec/unit/source/yaml_spec.rb +42 -0
  56. metadata +64 -22
  57. data/MAINTAINERS +0 -18
  58. data/docker/distelli-manifest.yml +0 -9
  59. data/integration/scripts/README.mkd +0 -86
  60. data/integration/scripts/setup_r10k_env_centos5.sh +0 -23
  61. data/integration/scripts/setup_r10k_env_centos6.sh +0 -23
  62. data/integration/scripts/setup_r10k_env_rhel7.sh +0 -23
  63. data/integration/scripts/setup_r10k_env_sles11.sh +0 -23
  64. data/integration/scripts/setup_r10k_env_sles12.sh +0 -23
  65. data/integration/scripts/setup_r10k_env_ubuntu1004.sh +0 -23
  66. data/integration/scripts/setup_r10k_env_ubuntu1204.sh +0 -23
  67. data/integration/scripts/setup_r10k_env_ubuntu1404.sh +0 -23
@@ -72,7 +72,11 @@ module R10K
72
72
  end
73
73
 
74
74
  def allowed_initialize_opts
75
- super.merge(environment: true, 'no-force': :self, 'generate-types': :self, 'puppet-path': :self)
75
+ super.merge(environment: true,
76
+ cachedir: :self,
77
+ 'no-force': :self,
78
+ 'generate-types': :self,
79
+ 'puppet-path': :self)
76
80
  end
77
81
  end
78
82
  end
@@ -43,10 +43,10 @@ module R10K
43
43
  config_settings = settings_from_config(@opts[:config])
44
44
 
45
45
  overrides = {}
46
- overrides[:cachedir] = @opts[:cachedir] unless @opts[:cachedir].nil?
47
- overrides[:deploy] = {} if @opts[:'puppet-path'] || @opts[:'generate-types']
48
- overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] unless @opts[:'puppet-path'].nil?
49
- overrides[:deploy][:generate_types] = @opts[:'generate-types'] unless @opts[:'generate-types'].nil?
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][:generate_types] = @opts[:'generate-types'] if @opts.key?(:'generate-types')
50
50
 
51
51
  with_overrides = config_settings.merge(overrides) do |key, oldval, newval|
52
52
  newval = oldval.merge(newval) if oldval.is_a? Hash
@@ -18,7 +18,7 @@ module R10K::CLI
18
18
 
19
19
  description <<-DESCRIPTION
20
20
  `r10k deploy` implements the Git branch to Puppet environment workflow
21
- (https://puppetlabs.com/blog/git-workflow-and-puppet-environments/).
21
+ (https://puppet.com/docs/puppet/latest/environments_about.html).
22
22
  DESCRIPTION
23
23
 
24
24
  required nil, :cachedir, 'Specify a cachedir, overriding the value in config'
@@ -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
@@ -212,14 +212,14 @@ module R10K
212
212
 
213
213
  # Remove the temporary directory used for unpacking the module.
214
214
  def cleanup_unpack_path
215
- if unpack_path.exist?
215
+ if unpack_path.parent.exist?
216
216
  unpack_path.parent.rmtree
217
217
  end
218
218
  end
219
219
 
220
220
  # Remove the downloaded module release.
221
221
  def cleanup_download_path
222
- if download_path.exist?
222
+ if download_path.parent.exist?
223
223
  download_path.parent.rmtree
224
224
  end
225
225
  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,11 @@ 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'
40
+ require 'r10k/source/exec'
37
41
  end
38
42
  end
@@ -0,0 +1,51 @@
1
+ require 'r10k/util/subprocess'
2
+ require 'json'
3
+ require 'yaml'
4
+
5
+ class R10K::Source::Exec < R10K::Source::Hash
6
+ R10K::Source.register(:exec, self)
7
+
8
+ def initialize(name, basedir, options = {})
9
+ unless @command = options[:command]
10
+ raise ConfigError, _('Environment source %{name} missing required parameter: command') % {name: name}
11
+ end
12
+
13
+ # We haven't set the environments option yet. We will do that by
14
+ # overloading the #environments method.
15
+ super(name, basedir, options)
16
+ end
17
+
18
+ def environments_hash
19
+ @environments_hash ||= set_environments_hash(run_environments_command)
20
+ end
21
+
22
+ private
23
+
24
+ def run_environments_command
25
+ subproc = R10K::Util::Subprocess.new([@command])
26
+ subproc.raise_on_fail = true
27
+ subproc.logger = self.logger
28
+ procresult = subproc.execute
29
+
30
+ begin
31
+ environments = JSON.parse(procresult.stdout)
32
+ rescue JSON::ParserError => json_err
33
+ begin
34
+ environments = YAML.safe_load(procresult.stdout)
35
+ rescue Psych::SyntaxError => yaml_err
36
+ raise R10K::Error, _("Error parsing command output for exec source %{name}:\n" \
37
+ "Not valid JSON: %{j_msg}\n" \
38
+ "Not valid YAML: %{y_msg}\n" \
39
+ "Stdout:\n%{out}") % {name: name, j_msg: json_err.message, y_msg: yaml_err.message, out: procresult.stdout}
40
+ end
41
+ end
42
+
43
+ unless R10K::Source::Hash.valid_environments_hash?(environments)
44
+ raise R10K::Error, _("Environment source %{name} command %{cmd} did not return valid environment data.\n" \
45
+ 'Returned: %{data}') % {name: name, cmd: @command, data: environments}
46
+ end
47
+
48
+ # Return the resulting environments hash
49
+ environments
50
+ end
51
+ end
@@ -0,0 +1,182 @@
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
+ include R10K::Logging
124
+
125
+ # @param hash [Hash] A hash to validate.
126
+ # @return [Boolean] False if the hash is obviously invalid. A true return
127
+ # means _maybe_ it's valid.
128
+ def self.valid_environments_hash?(hash)
129
+ # TODO: more robust schema valiation
130
+ hash.is_a?(Hash)
131
+ end
132
+
133
+ # @param name [String] The identifier for this source.
134
+ # @param basedir [String] The base directory where the generated environments will be created.
135
+ # @param options [Hash] An additional set of options for this source. The
136
+ # semantics of this hash may depend on the source implementation.
137
+ #
138
+ # @option options [Boolean, String] :prefix If a String this becomes the prefix.
139
+ # If true, will use the source name as the prefix. All sources should respect this option.
140
+ # Defaults to false for no environment prefix.
141
+ # @option options [Hash] :environments The hash definition of environments
142
+ def initialize(name, basedir, options = {})
143
+ super(name, basedir, options)
144
+ end
145
+
146
+ # Set the environment hash for the source. The environment hash is what the
147
+ # source uses to generate enviroments.
148
+ # @param hash [Hash] The hash to sanitize and use as the source's environments.
149
+ # Should be formatted for use with R10K::Environment#from_hash.
150
+ def set_environments_hash(hash)
151
+ @environments_hash = hash.reduce({}) do |memo,(name,opts)|
152
+ R10K::Util::SymbolizeKeys.symbolize_keys!(opts)
153
+ memo.merge({
154
+ name => opts.merge({
155
+ :basedir => @basedir,
156
+ :dirname => R10K::Environment::Name.new(name, {prefix: @prefix, source: @name}).dirname
157
+ })
158
+ })
159
+ end
160
+ end
161
+
162
+ # Return the sanitized environments hash for this source. The environments
163
+ # hash should contain objects formatted for use with R10K::Environment#from_hash.
164
+ # If the hash does not exist it will be built based on @options.
165
+ def environments_hash
166
+ @environments_hash ||= set_environments_hash(@options.fetch(:environments, {}))
167
+ end
168
+
169
+ def environments
170
+ @environments ||= environments_hash.map do |name, hash|
171
+ R10K::Environment.from_hash(name, hash)
172
+ end
173
+ end
174
+
175
+ # List all environments that should exist in the basedir for this source
176
+ # @note This is required by {R10K::Util::Basedir}
177
+ # @return [Array<String>]
178
+ def desired_contents
179
+ environments.map {|env| env.dirname }
180
+ end
181
+
182
+ end