r10k 3.9.0 → 3.10.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.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +1 -1
- data/.github/workflows/rspec_tests.yml +1 -1
- data/.github/workflows/stale.yml +19 -0
- data/CHANGELOG.mkd +24 -0
- data/doc/dynamic-environments/configuration.mkd +13 -6
- data/integration/Rakefile +1 -1
- data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
- data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +8 -14
- data/lib/r10k/action/base.rb +10 -0
- data/lib/r10k/action/deploy/display.rb +42 -9
- data/lib/r10k/action/deploy/environment.rb +70 -41
- data/lib/r10k/action/deploy/module.rb +51 -29
- data/lib/r10k/action/puppetfile/check.rb +3 -1
- data/lib/r10k/action/puppetfile/install.rb +20 -23
- data/lib/r10k/action/puppetfile/purge.rb +8 -2
- data/lib/r10k/action/runner.rb +11 -6
- data/lib/r10k/content_synchronizer.rb +83 -0
- data/lib/r10k/deployment.rb +1 -1
- data/lib/r10k/environment/base.rb +21 -1
- data/lib/r10k/environment/git.rb +0 -3
- data/lib/r10k/environment/svn.rb +4 -6
- data/lib/r10k/environment/with_modules.rb +18 -10
- data/lib/r10k/git/cache.rb +1 -1
- data/lib/r10k/initializers.rb +7 -0
- data/lib/r10k/module.rb +1 -1
- data/lib/r10k/module/base.rb +17 -1
- data/lib/r10k/module/forge.rb +29 -19
- data/lib/r10k/module/git.rb +23 -14
- data/lib/r10k/module/local.rb +1 -0
- data/lib/r10k/module/svn.rb +12 -9
- data/lib/r10k/module_loader/puppetfile.rb +195 -0
- data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
- data/lib/r10k/puppetfile.rb +111 -202
- data/lib/r10k/settings.rb +3 -0
- data/lib/r10k/source/base.rb +14 -0
- data/lib/r10k/source/git.rb +19 -6
- data/lib/r10k/source/hash.rb +1 -3
- data/lib/r10k/source/svn.rb +4 -2
- data/lib/r10k/util/cleaner.rb +21 -0
- data/lib/r10k/util/purgeable.rb +70 -8
- data/lib/r10k/version.rb +1 -1
- data/locales/r10k.pot +67 -71
- data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
- data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
- data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
- data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
- data/spec/r10k-mocks/mock_source.rb +1 -1
- data/spec/shared-examples/puppetfile-action.rb +7 -7
- data/spec/unit/action/deploy/display_spec.rb +32 -6
- data/spec/unit/action/deploy/environment_spec.rb +85 -48
- data/spec/unit/action/deploy/module_spec.rb +163 -31
- data/spec/unit/action/puppetfile/check_spec.rb +2 -2
- data/spec/unit/action/puppetfile/install_spec.rb +31 -10
- data/spec/unit/action/puppetfile/purge_spec.rb +25 -5
- data/spec/unit/action/runner_spec.rb +49 -25
- data/spec/unit/git/cache_spec.rb +14 -0
- data/spec/unit/module/forge_spec.rb +23 -14
- data/spec/unit/module/git_spec.rb +8 -8
- data/spec/unit/module_loader/puppetfile_spec.rb +330 -0
- data/spec/unit/module_spec.rb +22 -5
- data/spec/unit/puppetfile_spec.rb +123 -203
- data/spec/unit/settings_spec.rb +6 -2
- data/spec/unit/util/purgeable_spec.rb +40 -14
- metadata +12 -2
@@ -0,0 +1,37 @@
|
|
1
|
+
module R10K
|
2
|
+
module ModuleLoader
|
3
|
+
class Puppetfile
|
4
|
+
class DSL
|
5
|
+
# A barebones implementation of the Puppetfile DSL
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
|
9
|
+
def initialize(librarian)
|
10
|
+
@librarian = librarian
|
11
|
+
end
|
12
|
+
|
13
|
+
def mod(name, args = nil)
|
14
|
+
if args.is_a?(Hash)
|
15
|
+
opts = args
|
16
|
+
else
|
17
|
+
opts = { version: args }
|
18
|
+
end
|
19
|
+
|
20
|
+
@librarian.add_module(name, opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def forge(location)
|
24
|
+
@librarian.set_forge(location)
|
25
|
+
end
|
26
|
+
|
27
|
+
def moduledir(location)
|
28
|
+
@librarian.set_moduledir(location)
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(method, *args)
|
32
|
+
raise NoMethodError, _("unrecognized declaration '%{method}'") % {method: method}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/r10k/puppetfile.rb
CHANGED
@@ -3,10 +3,18 @@ require 'pathname'
|
|
3
3
|
require 'r10k/module'
|
4
4
|
require 'r10k/util/purgeable'
|
5
5
|
require 'r10k/errors'
|
6
|
+
require 'r10k/content_synchronizer'
|
7
|
+
require 'r10k/module_loader/puppetfile/dsl'
|
8
|
+
require 'r10k/module_loader/puppetfile'
|
6
9
|
|
7
10
|
module R10K
|
11
|
+
|
12
|
+
# Deprecated, use R10K::ModuleLoader::Puppetfile#load to load content,
|
13
|
+
# provide the `:modules` key of the returned Hash to
|
14
|
+
# R10K::ContentSynchronizer (either the `serial_sync` or `concurrent_sync`)
|
15
|
+
# and the remaining keys (`:managed_directories`, `:desired_contents`, and
|
16
|
+
# `:purge_exclusions`) to R10K::Util::Cleaner.
|
8
17
|
class Puppetfile
|
9
|
-
# Defines the data members of a Puppetfile
|
10
18
|
|
11
19
|
include R10K::Settings::Mixin
|
12
20
|
|
@@ -18,287 +26,188 @@ class Puppetfile
|
|
18
26
|
# @return [String] The URL to use for the Puppet Forge
|
19
27
|
attr_reader :forge
|
20
28
|
|
21
|
-
# @!attribute [r] modules
|
22
|
-
# @return [Array<R10K::Module>]
|
23
|
-
attr_reader :modules
|
24
|
-
|
25
29
|
# @!attribute [r] basedir
|
26
30
|
# @return [String] The base directory that contains the Puppetfile
|
27
31
|
attr_reader :basedir
|
28
32
|
|
29
|
-
# @!attribute [r] moduledir
|
30
|
-
# @return [String] The directory to install the modules #{basedir}/modules
|
31
|
-
attr_reader :moduledir
|
32
|
-
|
33
33
|
# @!attrbute [r] puppetfile_path
|
34
34
|
# @return [String] The path to the Puppetfile
|
35
35
|
attr_reader :puppetfile_path
|
36
36
|
|
37
|
-
# @!attribute [
|
37
|
+
# @!attribute [r] environment
|
38
38
|
# @return [R10K::Environment] Optional R10K::Environment that this Puppetfile belongs to.
|
39
|
-
|
39
|
+
attr_reader :environment
|
40
40
|
|
41
41
|
# @!attribute [rw] force
|
42
42
|
# @return [Boolean] Overwrite any locally made changes
|
43
43
|
attr_accessor :force
|
44
44
|
|
45
|
+
# @!attribute [r] overrides
|
46
|
+
# @return [Hash] Various settings overridden from normal configs
|
47
|
+
attr_reader :overrides
|
48
|
+
|
49
|
+
# @!attribute [r] loader
|
50
|
+
# @return [R10K::ModuleLoader::Puppetfile] The internal module loader
|
51
|
+
attr_reader :loader
|
52
|
+
|
45
53
|
# @param [String] basedir
|
46
|
-
# @param [String]
|
47
|
-
#
|
48
|
-
# @param [String]
|
49
|
-
# @param [
|
50
|
-
|
54
|
+
# @param [Hash, String, nil] options_or_moduledir The directory to install the modules or a Hash of options.
|
55
|
+
# Usage as moduledir is deprecated. Only use as options, defaults to nil
|
56
|
+
# @param [String, nil] puppetfile_path Deprecated - The path to the Puppetfile, defaults to nil
|
57
|
+
# @param [String, nil] puppetfile_name Deprecated - The name of the Puppetfile, defaults to nil
|
58
|
+
# @param [Boolean, nil] force Deprecated - Shall we overwrite locally made changes?
|
59
|
+
def initialize(basedir, options_or_moduledir = nil, deprecated_path_arg = nil, deprecated_name_arg = nil, deprecated_force_arg = nil)
|
51
60
|
@basedir = basedir
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
61
|
+
if options_or_moduledir.is_a? Hash
|
62
|
+
options = options_or_moduledir
|
63
|
+
deprecated_moduledir_arg = nil
|
64
|
+
else
|
65
|
+
options = {}
|
66
|
+
deprecated_moduledir_arg = options_or_moduledir
|
67
|
+
end
|
68
|
+
|
69
|
+
@force = deprecated_force_arg || options.delete(:force) || false
|
70
|
+
@moduledir = deprecated_moduledir_arg || options.delete(:moduledir) || File.join(basedir, 'modules')
|
71
|
+
@puppetfile_name = deprecated_name_arg || options.delete(:puppetfile_name) || 'Puppetfile'
|
72
|
+
@puppetfile_path = deprecated_path_arg || options.delete(:puppetfile_path) || File.join(basedir, @puppetfile_name)
|
73
|
+
@environment = options.delete(:environment)
|
74
|
+
|
75
|
+
@overrides = options.delete(:overrides) || {}
|
76
|
+
@default_branch_override = @overrides.dig(:environments, :default_branch_override)
|
56
77
|
|
57
78
|
logger.info _("Using Puppetfile '%{puppetfile}'") % {puppetfile: @puppetfile_path}
|
58
79
|
|
59
|
-
@modules = []
|
60
|
-
@managed_content = {}
|
61
80
|
@forge = 'forgeapi.puppetlabs.com'
|
62
81
|
|
82
|
+
@loader = ::R10K::ModuleLoader::Puppetfile.new(
|
83
|
+
basedir: @basedir,
|
84
|
+
moduledir: @moduledir,
|
85
|
+
puppetfile: @puppetfile_path,
|
86
|
+
forge: @forge,
|
87
|
+
overrides: @overrides,
|
88
|
+
environment: @environment
|
89
|
+
)
|
90
|
+
|
91
|
+
@loaded_content = {
|
92
|
+
modules: [],
|
93
|
+
managed_directories: [],
|
94
|
+
desired_contents: [],
|
95
|
+
purge_exclusions: []
|
96
|
+
}
|
97
|
+
|
63
98
|
@loaded = false
|
64
99
|
end
|
65
100
|
|
101
|
+
# @param [String] default_branch_override The default branch to use
|
102
|
+
# instead of one specified in the module declaration, if applicable.
|
103
|
+
# Deprecated, use R10K::ModuleLoader::Puppetfile directly and pass
|
104
|
+
# the default_branch_override as an option on initialization.
|
66
105
|
def load(default_branch_override = nil)
|
67
|
-
|
68
|
-
|
69
|
-
self.load!(default_branch_override)
|
106
|
+
if self.loaded?
|
107
|
+
return @loaded_content
|
70
108
|
else
|
71
|
-
|
109
|
+
if !File.readable?(@puppetfile_path)
|
110
|
+
logger.debug _("Puppetfile %{path} missing or unreadable") % {path: @puppetfile_path.inspect}
|
111
|
+
else
|
112
|
+
self.load!(default_branch_override)
|
113
|
+
end
|
72
114
|
end
|
73
115
|
end
|
74
116
|
|
117
|
+
# @param [String] default_branch_override The default branch to use
|
118
|
+
# instead of one specified in the module declaration, if applicable.
|
119
|
+
# Deprecated, use R10K::ModuleLoader::Puppetfile directly and pass
|
120
|
+
# the default_branch_override as an option on initialization.
|
75
121
|
def load!(default_branch_override = nil)
|
76
|
-
@default_branch_override = default_branch_override
|
77
122
|
|
78
|
-
|
79
|
-
|
123
|
+
if default_branch_override && (default_branch_override != @default_branch_override)
|
124
|
+
logger.warn("Mismatch between passed and initialized default branch overrides, preferring passed value.")
|
125
|
+
@loader.default_branch_override = default_branch_override
|
126
|
+
end
|
80
127
|
|
81
|
-
|
128
|
+
@loaded_content = @loader.load
|
82
129
|
@loaded = true
|
83
|
-
|
84
|
-
|
130
|
+
|
131
|
+
@loaded_content
|
85
132
|
end
|
86
133
|
|
87
134
|
def loaded?
|
88
135
|
@loaded
|
89
136
|
end
|
90
137
|
|
91
|
-
|
92
|
-
|
93
|
-
dupes = modules
|
94
|
-
.group_by { |mod| mod.name }
|
95
|
-
.select { |_, v| v.size > 1 }
|
96
|
-
.map(&:first)
|
97
|
-
unless dupes.empty?
|
98
|
-
msg = _('Puppetfiles cannot contain duplicate module names.')
|
99
|
-
msg += ' '
|
100
|
-
msg += _("Remove the duplicates of the following modules: %{dupes}" % { dupes: dupes.join(' ') })
|
101
|
-
raise R10K::Error.new(msg)
|
102
|
-
end
|
138
|
+
def modules
|
139
|
+
@loaded_content[:modules]
|
103
140
|
end
|
104
141
|
|
105
|
-
# @
|
106
|
-
def
|
107
|
-
@
|
142
|
+
# @see R10K::ModuleLoader::Puppetfile#add_module for upcoming signature changes
|
143
|
+
def add_module(name, args)
|
144
|
+
@loader.add_module(name, args)
|
108
145
|
end
|
109
146
|
|
110
|
-
|
111
|
-
|
112
|
-
@moduledir = if Pathname.new(moduledir).absolute?
|
113
|
-
moduledir
|
114
|
-
else
|
115
|
-
File.join(basedir, moduledir)
|
116
|
-
end
|
147
|
+
def set_moduledir(dir)
|
148
|
+
@loader.set_moduledir(dir)
|
117
149
|
end
|
118
150
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
if args.is_a?(Hash) && install_path = args.delete(:install_path)
|
123
|
-
install_path = resolve_install_path(install_path)
|
124
|
-
validate_install_path(install_path, name)
|
125
|
-
else
|
126
|
-
install_path = @moduledir
|
127
|
-
end
|
128
|
-
|
129
|
-
if args.is_a?(Hash) && @default_branch_override != nil
|
130
|
-
args[:default_branch_override] = @default_branch_override
|
131
|
-
end
|
132
|
-
|
133
|
-
mod = R10K::Module.new(name, install_path, args, @environment)
|
134
|
-
mod.origin = :puppetfile
|
135
|
-
|
136
|
-
# Do not load modules if they would conflict with the attached
|
137
|
-
# environment
|
138
|
-
if environment && environment.module_conflicts?(mod)
|
139
|
-
mod = nil
|
140
|
-
return @modules
|
141
|
-
end
|
151
|
+
def set_forge(forge)
|
152
|
+
@loader.set_forge(forge)
|
153
|
+
end
|
142
154
|
|
143
|
-
|
144
|
-
@
|
145
|
-
|
155
|
+
def moduledir
|
156
|
+
@loader.moduledir
|
157
|
+
end
|
146
158
|
|
147
|
-
|
159
|
+
def environment=(env)
|
160
|
+
@loader.environment = env
|
161
|
+
@environment = env
|
148
162
|
end
|
149
163
|
|
150
164
|
include R10K::Util::Purgeable
|
151
165
|
|
152
166
|
def managed_directories
|
153
|
-
self.load
|
167
|
+
self.load
|
154
168
|
|
155
|
-
|
156
|
-
dirs.delete(real_basedir)
|
157
|
-
dirs
|
169
|
+
@loaded_content[:managed_directories]
|
158
170
|
end
|
159
171
|
|
160
172
|
# Returns an array of the full paths to all the content being managed.
|
161
173
|
# @note This implements a required method for the Purgeable mixin
|
162
174
|
# @return [Array<String>]
|
163
175
|
def desired_contents
|
164
|
-
self.load
|
176
|
+
self.load
|
165
177
|
|
166
|
-
@
|
167
|
-
modnames.collect { |name| File.join(install_path, name) }
|
168
|
-
end
|
178
|
+
@loaded_content[:desired_contents]
|
169
179
|
end
|
170
180
|
|
171
181
|
def purge_exclusions
|
172
|
-
|
173
|
-
|
174
|
-
if environment && environment.respond_to?(:desired_contents)
|
175
|
-
exclusions += environment.desired_contents
|
176
|
-
end
|
182
|
+
self.load
|
177
183
|
|
178
|
-
|
184
|
+
@loaded_content[:purge_exclusions]
|
179
185
|
end
|
180
186
|
|
181
187
|
def accept(visitor)
|
182
188
|
pool_size = self.settings[:pool_size]
|
183
189
|
if pool_size > 1
|
184
|
-
concurrent_accept(visitor, pool_size)
|
190
|
+
R10K::ContentSynchronizer.concurrent_accept(modules, visitor, self, pool_size, logger)
|
185
191
|
else
|
186
|
-
serial_accept(visitor)
|
192
|
+
R10K::ContentSynchronizer.serial_accept(modules, visitor, self)
|
187
193
|
end
|
188
194
|
end
|
189
195
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def concurrent_accept(visitor, pool_size)
|
201
|
-
logger.debug _("Updating modules with %{pool_size} threads") % {pool_size: pool_size}
|
202
|
-
mods_queue = modules_queue(visitor)
|
203
|
-
thread_pool = pool_size.times.map { visitor_thread(visitor, mods_queue) }
|
204
|
-
thread_exception = nil
|
205
|
-
|
206
|
-
# If any threads raise an exception the deployment is considered a failure.
|
207
|
-
# In that event clear the queue, wait for other threads to finish their
|
208
|
-
# current work, then re-raise the first exception caught.
|
209
|
-
begin
|
210
|
-
thread_pool.each(&:join)
|
211
|
-
rescue => e
|
212
|
-
logger.error _("Error during concurrent deploy of a module: %{message}") % {message: e.message}
|
213
|
-
mods_queue.clear
|
214
|
-
thread_exception ||= e
|
215
|
-
retry
|
216
|
-
ensure
|
217
|
-
raise thread_exception unless thread_exception.nil?
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def modules_queue(visitor)
|
222
|
-
Queue.new.tap do |queue|
|
223
|
-
visitor.visit(:puppetfile, self) do
|
224
|
-
modules_by_cachedir = modules.group_by { |mod| mod.cachedir }
|
225
|
-
modules_without_vcs_cachedir = modules_by_cachedir.delete(:none) || []
|
226
|
-
|
227
|
-
modules_without_vcs_cachedir.each {|mod| queue << Array(mod) }
|
228
|
-
modules_by_cachedir.values.each {|mods| queue << mods }
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
public :modules_queue
|
233
|
-
|
234
|
-
def visitor_thread(visitor, mods_queue)
|
235
|
-
Thread.new do
|
236
|
-
begin
|
237
|
-
while mods = mods_queue.pop(true) do
|
238
|
-
mods.each {|mod| mod.accept(visitor) }
|
239
|
-
end
|
240
|
-
rescue ThreadError => e
|
241
|
-
logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
|
242
|
-
Thread.exit
|
243
|
-
rescue => e
|
244
|
-
Thread.main.raise(e)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def puppetfile_contents
|
250
|
-
File.read(@puppetfile_path)
|
251
|
-
end
|
252
|
-
|
253
|
-
def resolve_install_path(path)
|
254
|
-
pn = Pathname.new(path)
|
255
|
-
|
256
|
-
unless pn.absolute?
|
257
|
-
pn = Pathname.new(File.join(basedir, path))
|
196
|
+
def sync
|
197
|
+
pool_size = self.settings[:pool_size]
|
198
|
+
if pool_size > 1
|
199
|
+
R10K::ContentSynchronizer.concurrent_sync(modules, pool_size, logger)
|
200
|
+
else
|
201
|
+
R10K::ContentSynchronizer.serial_sync(modules)
|
258
202
|
end
|
259
|
-
|
260
|
-
# .cleanpath is as good as we can do without touching the filesystem.
|
261
|
-
# The .realpath methods will also choke if some of the intermediate
|
262
|
-
# paths are missing, even though we will create them later as needed.
|
263
|
-
pn.cleanpath.to_s
|
264
203
|
end
|
265
204
|
|
266
|
-
|
267
|
-
unless /^#{Regexp.escape(real_basedir)}.*/ =~ path
|
268
|
-
raise R10K::Error.new("Puppetfile cannot manage content '#{modname}' outside of containing environment: #{path} is not within #{real_basedir}")
|
269
|
-
end
|
270
|
-
|
271
|
-
true
|
272
|
-
end
|
205
|
+
private
|
273
206
|
|
274
207
|
def real_basedir
|
275
208
|
Pathname.new(basedir).cleanpath.to_s
|
276
209
|
end
|
277
210
|
|
278
|
-
|
279
|
-
# A barebones implementation of the Puppetfile DSL
|
280
|
-
#
|
281
|
-
# @api private
|
282
|
-
|
283
|
-
def initialize(librarian)
|
284
|
-
@librarian = librarian
|
285
|
-
end
|
286
|
-
|
287
|
-
def mod(name, args = nil)
|
288
|
-
@librarian.add_module(name, args)
|
289
|
-
end
|
290
|
-
|
291
|
-
def forge(location)
|
292
|
-
@librarian.set_forge(location)
|
293
|
-
end
|
294
|
-
|
295
|
-
def moduledir(location)
|
296
|
-
@librarian.set_moduledir(location)
|
297
|
-
end
|
298
|
-
|
299
|
-
def method_missing(method, *args)
|
300
|
-
raise NoMethodError, _("unrecognized declaration '%{method}'") % {method: method}
|
301
|
-
end
|
302
|
-
end
|
211
|
+
DSL = R10K::ModuleLoader::Puppetfile::DSL
|
303
212
|
end
|
304
213
|
end
|
data/lib/r10k/settings.rb
CHANGED
@@ -92,6 +92,9 @@ module R10K
|
|
92
92
|
URIDefinition.new(:baseurl, {
|
93
93
|
:desc => "The URL to the Puppet Forge to use for downloading modules."
|
94
94
|
}),
|
95
|
+
Definition.new(:authorization_token, {
|
96
|
+
:desc => "The token for Puppet Forge authorization. Leave blank for unauthorized or license-based connections."
|
97
|
+
})
|
95
98
|
])
|
96
99
|
end
|
97
100
|
|