r10k 3.11.0 → 3.12.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/CHANGELOG.mkd +9 -0
- data/doc/dynamic-environments/configuration.mkd +7 -3
- data/doc/dynamic-environments/usage.mkd +26 -0
- data/doc/puppetfile.mkd +3 -4
- data/integration/tests/basic_functionality/basic_deployment.rb +176 -0
- data/lib/r10k/action/deploy/environment.rb +8 -1
- data/lib/r10k/action/deploy/module.rb +11 -6
- data/lib/r10k/action/puppetfile/check.rb +7 -5
- data/lib/r10k/action/puppetfile/install.rb +22 -16
- data/lib/r10k/action/puppetfile/purge.rb +12 -9
- data/lib/r10k/cli/deploy.rb +1 -0
- data/lib/r10k/cli/puppetfile.rb +0 -1
- data/lib/r10k/content_synchronizer.rb +16 -4
- data/lib/r10k/environment/base.rb +64 -11
- data/lib/r10k/environment/with_modules.rb +6 -10
- data/lib/r10k/git/stateful_repository.rb +7 -0
- data/lib/r10k/initializers.rb +1 -7
- data/lib/r10k/module/base.rb +5 -1
- data/lib/r10k/module/definition.rb +64 -0
- data/lib/r10k/module/forge.rb +10 -2
- data/lib/r10k/module/git.rb +22 -1
- data/lib/r10k/module/local.rb +2 -3
- data/lib/r10k/module/svn.rb +10 -0
- data/lib/r10k/module.rb +20 -2
- data/lib/r10k/module_loader/puppetfile/dsl.rb +8 -3
- data/lib/r10k/module_loader/puppetfile.rb +95 -29
- data/lib/r10k/puppetfile.rb +1 -2
- data/lib/r10k/settings.rb +11 -0
- data/lib/r10k/version.rb +1 -1
- data/locales/r10k.pot +75 -47
- data/spec/fixtures/unit/puppetfile/forge-override/Puppetfile +8 -0
- data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile +9 -0
- data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile.new +9 -0
- data/spec/r10k-mocks/mock_env.rb +3 -0
- data/spec/r10k-mocks/mock_source.rb +7 -3
- data/spec/unit/action/deploy/environment_spec.rb +80 -30
- data/spec/unit/action/deploy/module_spec.rb +50 -62
- data/spec/unit/action/puppetfile/check_spec.rb +17 -5
- data/spec/unit/action/puppetfile/install_spec.rb +42 -36
- data/spec/unit/action/puppetfile/purge_spec.rb +15 -17
- data/spec/unit/action/runner_spec.rb +0 -8
- data/spec/unit/environment/base_spec.rb +30 -17
- data/spec/unit/environment/git_spec.rb +2 -2
- data/spec/unit/environment/svn_spec.rb +4 -3
- data/spec/unit/environment/with_modules_spec.rb +2 -1
- data/spec/unit/module/base_spec.rb +8 -8
- data/spec/unit/module/forge_spec.rb +32 -4
- data/spec/unit/module/git_spec.rb +51 -10
- data/spec/unit/module/svn_spec.rb +18 -6
- data/spec/unit/module_loader/puppetfile_spec.rb +90 -30
- data/spec/unit/puppetfile_spec.rb +2 -2
- data/spec/unit/settings_spec.rb +25 -2
- metadata +7 -2
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'r10k/puppetfile'
|
2
|
-
require 'r10k/util/cleaner'
|
3
1
|
require 'r10k/action/base'
|
4
2
|
require 'r10k/errors/formatting'
|
3
|
+
require 'r10k/module_loader/puppetfile'
|
4
|
+
require 'r10k/util/cleaner'
|
5
5
|
|
6
6
|
module R10K
|
7
7
|
module Action
|
@@ -9,13 +9,16 @@ module R10K
|
|
9
9
|
class Purge < R10K::Action::Base
|
10
10
|
|
11
11
|
def call
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
options = { basedir: @root }
|
13
|
+
|
14
|
+
options[:moduledir] = @moduledir if @moduledir
|
15
|
+
options[:puppetfile] = @puppetfile if @puppetfile
|
16
|
+
|
17
|
+
loader = R10K::ModuleLoader::Puppetfile.new(**options)
|
18
|
+
loaded_content = loader.load!
|
19
|
+
R10K::Util::Cleaner.new(loaded_content[:managed_directories],
|
20
|
+
loaded_content[:desired_contents],
|
21
|
+
loaded_content[:purge_exclusions]).purge!
|
19
22
|
|
20
23
|
true
|
21
24
|
rescue => e
|
data/lib/r10k/cli/deploy.rb
CHANGED
@@ -69,6 +69,7 @@ scheduled. On subsequent deployments, Puppetfile deployment will default to off.
|
|
69
69
|
|
70
70
|
flag :p, :puppetfile, 'Deploy modules (deprecated, use -m)'
|
71
71
|
flag :m, :modules, 'Deploy modules'
|
72
|
+
flag nil, :incremental, 'Used with the --modules flag, only update those modules whose definition has changed or whose definition allows the version to float'
|
72
73
|
option nil, :'default-branch-override', 'Specify a branchname to override the default branch in the puppetfile',
|
73
74
|
argument: :required
|
74
75
|
|
data/lib/r10k/cli/puppetfile.rb
CHANGED
@@ -8,24 +8,31 @@ module R10K
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.serial_sync(modules)
|
11
|
+
updated_modules = []
|
11
12
|
modules.each do |mod|
|
12
|
-
mod.sync
|
13
|
+
updated = mod.sync
|
14
|
+
updated_modules << mod.name if updated
|
13
15
|
end
|
16
|
+
updated_modules
|
14
17
|
end
|
15
18
|
|
19
|
+
# Returns a Queue of the names of modules actually updated
|
16
20
|
def self.concurrent_accept(modules, visitor, loader, pool_size, logger)
|
17
21
|
mods_queue = modules_visit_queue(modules, visitor, loader)
|
18
22
|
sync_queue(mods_queue, pool_size, logger)
|
19
23
|
end
|
20
24
|
|
25
|
+
# Returns a Queue of the names of modules actually updated
|
21
26
|
def self.concurrent_sync(modules, pool_size, logger)
|
22
27
|
mods_queue = modules_sync_queue(modules)
|
23
28
|
sync_queue(mods_queue, pool_size, logger)
|
24
29
|
end
|
25
30
|
|
31
|
+
# Returns a Queue of the names of modules actually updated
|
26
32
|
def self.sync_queue(mods_queue, pool_size, logger)
|
27
33
|
logger.debug _("Updating modules with %{pool_size} threads") % {pool_size: pool_size}
|
28
|
-
|
34
|
+
updated_modules = Queue.new
|
35
|
+
thread_pool = pool_size.times.map { sync_thread(mods_queue, logger, updated_modules) }
|
29
36
|
thread_exception = nil
|
30
37
|
|
31
38
|
# If any threads raise an exception the deployment is considered a failure.
|
@@ -33,6 +40,8 @@ module R10K
|
|
33
40
|
# current work, then re-raise the first exception caught.
|
34
41
|
begin
|
35
42
|
thread_pool.each(&:join)
|
43
|
+
# Return the list of all modules that were actually updated
|
44
|
+
updated_modules
|
36
45
|
rescue => e
|
37
46
|
logger.error _("Error during concurrent deploy of a module: %{message}") % {message: e.message}
|
38
47
|
mods_queue.clear
|
@@ -65,11 +74,14 @@ module R10K
|
|
65
74
|
modules_by_cachedir.values.each {|mods| queue << mods }
|
66
75
|
end
|
67
76
|
|
68
|
-
def self.sync_thread(mods_queue, logger)
|
77
|
+
def self.sync_thread(mods_queue, logger, updated_modules)
|
69
78
|
Thread.new do
|
70
79
|
begin
|
71
80
|
while mods = mods_queue.pop(true) do
|
72
|
-
mods.each
|
81
|
+
mods.each do |mod|
|
82
|
+
updated = mod.sync
|
83
|
+
updated_modules << mod.name if updated
|
84
|
+
end
|
73
85
|
end
|
74
86
|
rescue ThreadError => e
|
75
87
|
logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
|
@@ -1,5 +1,8 @@
|
|
1
|
-
require 'r10k/
|
1
|
+
require 'r10k/content_synchronizer'
|
2
2
|
require 'r10k/logging'
|
3
|
+
require 'r10k/module_loader/puppetfile'
|
4
|
+
require 'r10k/util/cleaner'
|
5
|
+
require 'r10k/util/subprocess'
|
3
6
|
|
4
7
|
# This class defines a common interface for environment implementations.
|
5
8
|
#
|
@@ -34,6 +37,10 @@ class R10K::Environment::Base
|
|
34
37
|
# @return [String] The puppetfile name (relative)
|
35
38
|
attr_reader :puppetfile_name
|
36
39
|
|
40
|
+
attr_reader :managed_directories, :desired_contents
|
41
|
+
|
42
|
+
attr_reader :loader
|
43
|
+
|
37
44
|
# Initialize the given environment.
|
38
45
|
#
|
39
46
|
# @param name [String] The unique name describing this environment.
|
@@ -57,6 +64,20 @@ class R10K::Environment::Base
|
|
57
64
|
force: @overrides.dig(:modules, :force),
|
58
65
|
puppetfile_name: @puppetfile_name})
|
59
66
|
@puppetfile.environment = self
|
67
|
+
|
68
|
+
loader_options = { basedir: @full_path, overrides: @overrides, environment: self }
|
69
|
+
loader_options[:puppetfile] = @puppetfile_name if @puppetfile_name
|
70
|
+
|
71
|
+
@loader = R10K::ModuleLoader::Puppetfile.new(**loader_options)
|
72
|
+
|
73
|
+
if @overrides.dig(:environments, :incremental)
|
74
|
+
@loader.load_metadata
|
75
|
+
end
|
76
|
+
|
77
|
+
@base_modules = nil
|
78
|
+
@purge_exclusions = nil
|
79
|
+
@managed_directories = [ @full_path ]
|
80
|
+
@desired_contents = []
|
60
81
|
end
|
61
82
|
|
62
83
|
# Synchronize the given environment.
|
@@ -106,8 +127,11 @@ class R10K::Environment::Base
|
|
106
127
|
# @return [Array<R10K::Module::Base>] All modules defined in the Puppetfile
|
107
128
|
# associated with this environment.
|
108
129
|
def modules
|
109
|
-
@
|
110
|
-
|
130
|
+
if @base_modules.nil?
|
131
|
+
load_puppetfile_modules
|
132
|
+
end
|
133
|
+
|
134
|
+
@base_modules
|
111
135
|
end
|
112
136
|
|
113
137
|
# @return [Array<R10K::Module::Base>] Whether or not the given module
|
@@ -123,29 +147,50 @@ class R10K::Environment::Base
|
|
123
147
|
end
|
124
148
|
end
|
125
149
|
|
150
|
+
|
151
|
+
# Returns a Queue of the names of modules actually updated
|
126
152
|
def deploy
|
127
|
-
|
153
|
+
if @base_modules.nil?
|
154
|
+
load_puppetfile_modules
|
155
|
+
end
|
128
156
|
|
129
|
-
|
157
|
+
if ! @base_modules.empty?
|
158
|
+
pool_size = @overrides.dig(:modules, :pool_size)
|
159
|
+
updated_modules = R10K::ContentSynchronizer.concurrent_sync(@base_modules, pool_size, logger)
|
160
|
+
end
|
130
161
|
|
131
162
|
if (@overrides.dig(:purging, :purge_levels) || []).include?(:puppetfile)
|
132
163
|
logger.debug("Purging unmanaged Puppetfile content for environment '#{dirname}'...")
|
133
|
-
|
134
|
-
puppetfile.desired_contents,
|
135
|
-
puppetfile.purge_exclusions).purge!
|
164
|
+
@puppetfile_cleaner.purge!
|
136
165
|
end
|
166
|
+
|
167
|
+
updated_modules
|
168
|
+
end
|
169
|
+
|
170
|
+
def load_puppetfile_modules
|
171
|
+
loaded_content = @loader.load
|
172
|
+
@base_modules = loaded_content[:modules]
|
173
|
+
|
174
|
+
@purge_exclusions = determine_purge_exclusions(loaded_content[:managed_directories],
|
175
|
+
loaded_content[:desired_contents])
|
176
|
+
|
177
|
+
@puppetfile_cleaner = R10K::Util::Cleaner.new(loaded_content[:managed_directories],
|
178
|
+
loaded_content[:desired_contents],
|
179
|
+
loaded_content[:purge_exclusions])
|
137
180
|
end
|
138
181
|
|
139
182
|
def whitelist(user_whitelist=[])
|
140
183
|
user_whitelist.collect { |pattern| File.join(@full_path, pattern) }
|
141
184
|
end
|
142
185
|
|
143
|
-
def
|
186
|
+
def determine_purge_exclusions(pf_managed_dirs = @puppetfile.managed_directories,
|
187
|
+
pf_desired_contents = @puppetfile.desired_contents)
|
188
|
+
|
144
189
|
list = [File.join(@full_path, '.r10k-deploy.json')].to_set
|
145
190
|
|
146
|
-
list +=
|
191
|
+
list += pf_managed_dirs
|
147
192
|
|
148
|
-
list +=
|
193
|
+
list += pf_desired_contents.flat_map do |item|
|
149
194
|
desired_tree = []
|
150
195
|
|
151
196
|
if File.directory?(item)
|
@@ -163,6 +208,14 @@ class R10K::Environment::Base
|
|
163
208
|
list.to_a
|
164
209
|
end
|
165
210
|
|
211
|
+
def purge_exclusions
|
212
|
+
if @purge_exclusions.nil?
|
213
|
+
load_puppetfile_modules
|
214
|
+
end
|
215
|
+
|
216
|
+
@purge_exclusions
|
217
|
+
end
|
218
|
+
|
166
219
|
def generate_types!
|
167
220
|
argv = [R10K::Settings.puppet_path, 'generate', 'types', '--environment', dirname, '--environmentpath', basedir, '--config', R10K::Settings.puppet_conf]
|
168
221
|
subproc = R10K::Util::Subprocess.new(argv)
|
@@ -23,6 +23,7 @@ class R10K::Environment::WithModules < R10K::Environment::Base
|
|
23
23
|
def initialize(name, basedir, dirname, options = {})
|
24
24
|
super
|
25
25
|
|
26
|
+
@all_modules = nil
|
26
27
|
@managed_content = {}
|
27
28
|
@modules = []
|
28
29
|
@moduledir = case options[:moduledir]
|
@@ -43,10 +44,12 @@ class R10K::Environment::WithModules < R10K::Environment::Base
|
|
43
44
|
# - The r10k environment object
|
44
45
|
# - A Puppetfile in the environment's content
|
45
46
|
def modules
|
46
|
-
|
47
|
+
if @all_modules.nil?
|
48
|
+
puppetfile_modules = super()
|
49
|
+
@all_modules = @modules + puppetfile_modules
|
50
|
+
end
|
47
51
|
|
48
|
-
|
49
|
-
@modules + puppetfile.modules
|
52
|
+
@all_modules
|
50
53
|
end
|
51
54
|
|
52
55
|
def module_conflicts?(mod_b)
|
@@ -126,13 +129,6 @@ class R10K::Environment::WithModules < R10K::Environment::Base
|
|
126
129
|
|
127
130
|
include R10K::Util::Purgeable
|
128
131
|
|
129
|
-
# Returns an array of the full paths that can be purged.
|
130
|
-
# @note This implements a required method for the Purgeable mixin
|
131
|
-
# @return [Array<String>]
|
132
|
-
def managed_directories
|
133
|
-
[@full_path]
|
134
|
-
end
|
135
|
-
|
136
132
|
# Returns an array of the full paths of filenames that should exist. Files
|
137
133
|
# inside managed_directories that are not listed in desired_contents will
|
138
134
|
# be purged.
|
@@ -35,6 +35,7 @@ class R10K::Git::StatefulRepository
|
|
35
35
|
@cache.resolve(ref)
|
36
36
|
end
|
37
37
|
|
38
|
+
# Returns true if the sync actually updated the repo, false otherwise
|
38
39
|
def sync(ref, force=true)
|
39
40
|
@cache.sync if sync_cache?(ref)
|
40
41
|
|
@@ -46,6 +47,7 @@ class R10K::Git::StatefulRepository
|
|
46
47
|
|
47
48
|
workdir_status = status(ref)
|
48
49
|
|
50
|
+
updated = true
|
49
51
|
case workdir_status
|
50
52
|
when :absent
|
51
53
|
logger.debug(_("Cloning %{repo_path} and checking out %{ref}") % {repo_path: @repo.path, ref: ref })
|
@@ -64,15 +66,20 @@ class R10K::Git::StatefulRepository
|
|
64
66
|
@repo.checkout(sha, {:force => force})
|
65
67
|
else
|
66
68
|
logger.warn(_("Skipping %{repo_path} due to local modifications") % {repo_path: @repo.path})
|
69
|
+
updated = false
|
67
70
|
end
|
68
71
|
else
|
69
72
|
logger.debug(_("%{repo_path} is already at Git ref %{ref}") % {repo_path: @repo.path, ref: ref })
|
73
|
+
updated = false
|
70
74
|
end
|
75
|
+
updated
|
71
76
|
end
|
72
77
|
|
73
78
|
def status(ref)
|
74
79
|
if !@repo.exist?
|
75
80
|
:absent
|
81
|
+
elsif !@cache.exist?
|
82
|
+
:mismatched
|
76
83
|
elsif !@repo.git_dir.exist?
|
77
84
|
:mismatched
|
78
85
|
elsif !@repo.git_dir.directory?
|
data/lib/r10k/initializers.rb
CHANGED
@@ -66,13 +66,7 @@ module R10K
|
|
66
66
|
def call
|
67
67
|
with_setting(:baseurl) { |value| PuppetForge.host = value }
|
68
68
|
with_setting(:proxy) { |value| PuppetForge::Connection.proxy = value }
|
69
|
-
with_setting(:authorization_token) { |value|
|
70
|
-
if @settings[:baseurl]
|
71
|
-
PuppetForge::Connection.authorization = value
|
72
|
-
else
|
73
|
-
raise R10K::Error, "Cannot specify a Forge authorization token without configuring a custom baseurl."
|
74
|
-
end
|
75
|
-
}
|
69
|
+
with_setting(:authorization_token) { |value| PuppetForge::Connection.authorization = value }
|
76
70
|
end
|
77
71
|
end
|
78
72
|
end
|
data/lib/r10k/module/base.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'r10k/module'
|
2
|
+
require 'r10k/logging'
|
2
3
|
require 'puppet_forge'
|
3
4
|
|
4
5
|
# This class defines a common interface for module implementations.
|
5
6
|
class R10K::Module::Base
|
6
7
|
|
8
|
+
include R10K::Logging
|
9
|
+
|
7
10
|
# @!attribute [r] title
|
8
11
|
# @return [String] The forward slash separated owner and name of the module
|
9
12
|
attr_reader :title
|
@@ -47,7 +50,7 @@ class R10K::Module::Base
|
|
47
50
|
|
48
51
|
# @param title [String]
|
49
52
|
# @param dirname [String]
|
50
|
-
# @param args [
|
53
|
+
# @param args [Hash]
|
51
54
|
def initialize(title, dirname, args, environment=nil)
|
52
55
|
@title = PuppetForge::V3.normalize_name(title)
|
53
56
|
@dirname = dirname
|
@@ -103,6 +106,7 @@ class R10K::Module::Base
|
|
103
106
|
|
104
107
|
# Synchronize this module with the indicated state.
|
105
108
|
# @param [Hash] opts Deprecated
|
109
|
+
# @return [Boolean] true if the module was updated, false otherwise
|
106
110
|
def sync(opts={})
|
107
111
|
raise NotImplementedError
|
108
112
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'r10k/module'
|
2
|
+
|
3
|
+
class R10K::Module::Definition < R10K::Module::Base
|
4
|
+
|
5
|
+
attr_reader :version
|
6
|
+
|
7
|
+
def initialize(name, dirname:, args:, implementation:, environment: nil)
|
8
|
+
@original_name = name
|
9
|
+
@original_args = args.dup
|
10
|
+
@implementation = implementation
|
11
|
+
@version = implementation.statically_defined_version(name, args)
|
12
|
+
|
13
|
+
super(name, dirname, args, environment)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_implementation
|
17
|
+
mod = @implementation.new(@title, @dirname, @original_args, @environment)
|
18
|
+
|
19
|
+
mod.origin = origin
|
20
|
+
mod.spec_deletable = spec_deletable
|
21
|
+
|
22
|
+
mod
|
23
|
+
end
|
24
|
+
|
25
|
+
# syncing is a noop for module definitions
|
26
|
+
# Returns false to inidicate the module was not updated
|
27
|
+
def sync(args = {})
|
28
|
+
logger.debug1(_("Not updating module %{name}, assuming content unchanged") % {name: name})
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def status
|
33
|
+
:insync
|
34
|
+
end
|
35
|
+
|
36
|
+
def properties
|
37
|
+
type = nil
|
38
|
+
|
39
|
+
if @args[:type]
|
40
|
+
type = @args[:type]
|
41
|
+
elsif @args[:ref] || @args[:commit] || @args[:branch] || @args[:tag]
|
42
|
+
type = 'git'
|
43
|
+
elsif @args[:svn]
|
44
|
+
# This logic is clear and included for completeness sake, though at
|
45
|
+
# this time module definitions do not support SVN versions.
|
46
|
+
type = 'svn'
|
47
|
+
else
|
48
|
+
type = 'forge'
|
49
|
+
end
|
50
|
+
|
51
|
+
{
|
52
|
+
expected: version,
|
53
|
+
# We can't get the value for `actual` here because that requires the
|
54
|
+
# implementation (and potentially expensive operations by the
|
55
|
+
# implementation). Some consumers will check this value, if it exists
|
56
|
+
# and if not, fall back to the expected version. That is the correct
|
57
|
+
# behavior when assuming modules are unchanged, and why `actual` is set
|
58
|
+
# to `nil` here.
|
59
|
+
actual: nil,
|
60
|
+
type: type
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/lib/r10k/module/forge.rb
CHANGED
@@ -25,6 +25,10 @@ class R10K::Module::Forge < R10K::Module::Base
|
|
25
25
|
expected_version == :latest || expected_version.nil? || PuppetForge::Util.version_valid?(expected_version)
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.statically_defined_version(name, args)
|
29
|
+
args[:version] if args[:version].is_a?(String)
|
30
|
+
end
|
31
|
+
|
28
32
|
# @!attribute [r] metadata
|
29
33
|
# @api private
|
30
34
|
# @return [PuppetForge::Metadata]
|
@@ -35,8 +39,6 @@ class R10K::Module::Forge < R10K::Module::Base
|
|
35
39
|
# @return [PuppetForge::V3::Module] The Puppet Forge module metadata
|
36
40
|
attr_reader :v3_module
|
37
41
|
|
38
|
-
include R10K::Logging
|
39
|
-
|
40
42
|
include R10K::Util::Setopts
|
41
43
|
|
42
44
|
def initialize(title, dirname, opts, environment=nil)
|
@@ -58,18 +60,24 @@ class R10K::Module::Forge < R10K::Module::Base
|
|
58
60
|
end
|
59
61
|
|
60
62
|
# @param [Hash] opts Deprecated
|
63
|
+
# @return [Boolean] true if the module was updated, false otherwise
|
61
64
|
def sync(opts={})
|
65
|
+
updated = false
|
62
66
|
if should_sync?
|
63
67
|
case status
|
64
68
|
when :absent
|
65
69
|
install
|
70
|
+
updated = true
|
66
71
|
when :outdated
|
67
72
|
upgrade
|
73
|
+
updated = true
|
68
74
|
when :mismatched
|
69
75
|
reinstall
|
76
|
+
updated = true
|
70
77
|
end
|
71
78
|
maybe_delete_spec_dir
|
72
79
|
end
|
80
|
+
updated
|
73
81
|
end
|
74
82
|
|
75
83
|
def properties
|