modulesync 0.9.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +8 -3
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +1 -6
- data/.travis.yml +8 -5
- data/CHANGELOG.md +38 -1
- data/Gemfile +1 -0
- data/README.md +86 -15
- data/Rakefile +1 -0
- data/features/step_definitions/git_steps.rb +1 -1
- data/features/update.feature +79 -27
- data/lib/modulesync.rb +107 -33
- data/lib/modulesync/cli.rb +28 -6
- data/lib/modulesync/git.rb +5 -1
- data/lib/modulesync/pr/github.rb +47 -0
- data/lib/modulesync/pr/gitlab.rb +42 -0
- data/lib/modulesync/renderer.rb +4 -3
- data/lib/modulesync/settings.rb +1 -2
- data/lib/modulesync/util.rb +10 -0
- data/modulesync.gemspec +4 -2
- data/spec/unit/modulesync/pr/github_spec.rb +49 -0
- data/spec/unit/modulesync/pr/gitlab_spec.rb +81 -0
- data/spec/unit/modulesync_spec.rb +9 -1
- metadata +41 -8
data/lib/modulesync.rb
CHANGED
@@ -9,7 +9,7 @@ require 'modulesync/settings'
|
|
9
9
|
require 'modulesync/util'
|
10
10
|
require 'monkey_patches'
|
11
11
|
|
12
|
-
module ModuleSync
|
12
|
+
module ModuleSync # rubocop:disable Metrics/ModuleLength
|
13
13
|
include Constants
|
14
14
|
|
15
15
|
def self.config_defaults
|
@@ -22,31 +22,39 @@ module ModuleSync
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.local_file(config_path, file)
|
25
|
-
|
25
|
+
File.join(config_path, MODULE_FILES_DIR, file)
|
26
26
|
end
|
27
27
|
|
28
|
-
def self.module_file(project_root, puppet_module,
|
29
|
-
|
28
|
+
def self.module_file(project_root, namespace, puppet_module, *parts)
|
29
|
+
File.join(project_root, namespace, puppet_module, *parts)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
# List all template files.
|
33
|
+
#
|
34
|
+
# Only select *.erb files, and strip the extension. This way all the code will only have to handle bare paths,
|
35
|
+
# except when reading the actual ERB text
|
36
|
+
def self.find_template_files(local_template_dir)
|
37
|
+
if File.exist?(local_template_dir)
|
38
|
+
Find.find(local_template_dir).find_all { |p| p =~ /.erb$/ && !File.directory?(p) }
|
39
|
+
.collect { |p| p.chomp('.erb') }
|
40
|
+
.to_a
|
36
41
|
else
|
37
|
-
puts "#{
|
42
|
+
$stdout.puts "#{local_template_dir} does not exist." \
|
43
|
+
' Check that you are working in your module configs directory or' \
|
44
|
+
' that you have passed in the correct directory with -c.'
|
38
45
|
exit
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
42
|
-
def self.
|
43
|
-
|
49
|
+
def self.relative_names(file_list, path)
|
50
|
+
file_list.map { |file| file.sub(/#{path}/, '') }
|
44
51
|
end
|
45
52
|
|
46
53
|
def self.managed_modules(config_file, filter, negative_filter)
|
47
54
|
managed_modules = Util.parse_config(config_file)
|
48
55
|
if managed_modules.empty?
|
49
|
-
puts "No modules found in #{config_file}.
|
56
|
+
$stdout.puts "No modules found in #{config_file}." \
|
57
|
+
' Check that you specified the right :configs directory and :managed_modules_conf file.'
|
50
58
|
exit
|
51
59
|
end
|
52
60
|
managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
|
@@ -71,62 +79,93 @@ module ModuleSync
|
|
71
79
|
end
|
72
80
|
|
73
81
|
def self.manage_file(filename, settings, options)
|
82
|
+
namespace = settings.additional_settings[:namespace]
|
74
83
|
module_name = settings.additional_settings[:puppet_module]
|
75
84
|
configs = settings.build_file_configs(filename)
|
85
|
+
target_file = module_file(options[:project_root], namespace, module_name, filename)
|
76
86
|
if configs['delete']
|
77
|
-
Renderer.remove(
|
87
|
+
Renderer.remove(target_file)
|
78
88
|
else
|
79
89
|
templatename = local_file(options[:configs], filename)
|
80
90
|
begin
|
81
91
|
erb = Renderer.build(templatename)
|
82
|
-
template
|
83
|
-
|
92
|
+
# Meta data passed to the template as @metadata[:name]
|
93
|
+
metadata = {
|
94
|
+
:module_name => module_name,
|
95
|
+
:workdir => module_file(options[:project_root], namespace, module_name),
|
96
|
+
:target_file => target_file,
|
97
|
+
}
|
98
|
+
template = Renderer.render(erb, configs, metadata)
|
99
|
+
Renderer.sync(template, target_file)
|
84
100
|
rescue # rubocop:disable Lint/RescueWithoutErrorClass
|
85
|
-
|
101
|
+
$stderr.puts "Error while rendering #{filename}"
|
86
102
|
raise
|
87
103
|
end
|
88
104
|
end
|
89
105
|
end
|
90
106
|
|
91
107
|
def self.manage_module(puppet_module, module_files, module_options, defaults, options)
|
92
|
-
|
93
|
-
|
108
|
+
default_namespace = options[:namespace]
|
109
|
+
if module_options.is_a?(Hash) && module_options.key?(:namespace)
|
110
|
+
default_namespace = module_options[:namespace]
|
111
|
+
end
|
112
|
+
namespace, module_name = module_name(puppet_module, default_namespace)
|
113
|
+
git_repo = File.join(namespace, module_name)
|
94
114
|
unless options[:offline]
|
95
|
-
git_base
|
96
|
-
git_uri = "#{git_base}#{namespace}"
|
97
|
-
Git.pull(git_uri, module_name, options[:branch], options[:project_root], module_options || {})
|
115
|
+
Git.pull(options[:git_base], git_repo, options[:branch], options[:project_root], module_options || {})
|
98
116
|
end
|
99
|
-
|
117
|
+
|
118
|
+
module_configs = Util.parse_config(module_file(options[:project_root], namespace, module_name, MODULE_CONF_FILE))
|
100
119
|
settings = Settings.new(defaults[GLOBAL_DEFAULTS_KEY] || {},
|
101
120
|
defaults,
|
102
121
|
module_configs[GLOBAL_DEFAULTS_KEY] || {},
|
103
122
|
module_configs,
|
104
123
|
:puppet_module => module_name,
|
105
|
-
:git_base => git_base,
|
124
|
+
:git_base => options[:git_base],
|
106
125
|
:namespace => namespace)
|
107
126
|
settings.unmanaged_files(module_files).each do |filename|
|
108
|
-
puts "Not managing #{filename} in #{module_name}"
|
127
|
+
$stdout.puts "Not managing #{filename} in #{module_name}"
|
109
128
|
end
|
110
129
|
|
111
130
|
files_to_manage = settings.managed_files(module_files)
|
112
131
|
files_to_manage.each { |filename| manage_file(filename, settings, options) }
|
113
132
|
|
114
133
|
if options[:noop]
|
115
|
-
Git.update_noop(
|
134
|
+
Git.update_noop(git_repo, options)
|
116
135
|
elsif !options[:offline]
|
117
|
-
Git.update(
|
136
|
+
pushed = Git.update(git_repo, files_to_manage, options)
|
137
|
+
pushed && options[:pr] && pr(module_options).manage(namespace, module_name, options)
|
118
138
|
end
|
119
139
|
end
|
120
140
|
|
141
|
+
def self.config_path(file, options)
|
142
|
+
return file if Pathname.new(file).absolute?
|
143
|
+
File.join(options[:configs], file)
|
144
|
+
end
|
145
|
+
|
146
|
+
def config_path(file, options)
|
147
|
+
self.class.config_path(file, options)
|
148
|
+
end
|
149
|
+
|
121
150
|
def self.update(options)
|
122
151
|
options = config_defaults.merge(options)
|
123
|
-
defaults = Util.parse_config(
|
152
|
+
defaults = Util.parse_config(config_path(CONF_FILE, options))
|
153
|
+
if options[:pr]
|
154
|
+
unless options[:branch]
|
155
|
+
$stderr.puts 'A branch must be specified with --branch to use --pr!'
|
156
|
+
raise
|
157
|
+
end
|
158
|
+
|
159
|
+
@pr = create_pr_manager if options[:pr]
|
160
|
+
end
|
124
161
|
|
125
|
-
|
126
|
-
local_files =
|
127
|
-
module_files =
|
162
|
+
local_template_dir = config_path(MODULE_FILES_DIR, options)
|
163
|
+
local_files = find_template_files(local_template_dir)
|
164
|
+
module_files = relative_names(local_files, local_template_dir)
|
128
165
|
|
129
|
-
managed_modules = self.managed_modules(
|
166
|
+
managed_modules = self.managed_modules(config_path(options[:managed_modules_conf], options),
|
167
|
+
options[:filter],
|
168
|
+
options[:negative_filter])
|
130
169
|
|
131
170
|
errors = false
|
132
171
|
# managed_modules is either an array or a hash
|
@@ -134,12 +173,47 @@ module ModuleSync
|
|
134
173
|
begin
|
135
174
|
manage_module(puppet_module, module_files, module_options, defaults, options)
|
136
175
|
rescue # rubocop:disable Lint/RescueWithoutErrorClass
|
137
|
-
|
176
|
+
$stderr.puts "Error while updating #{puppet_module}"
|
138
177
|
raise unless options[:skip_broken]
|
139
178
|
errors = true
|
140
|
-
puts "Skipping #{puppet_module} as update process failed"
|
179
|
+
$stdout.puts "Skipping #{puppet_module} as update process failed"
|
141
180
|
end
|
142
181
|
end
|
143
182
|
exit 1 if errors && options[:fail_on_warnings]
|
144
183
|
end
|
184
|
+
|
185
|
+
def self.pr(module_options)
|
186
|
+
github_conf = module_options[:github]
|
187
|
+
gitlab_conf = module_options[:gitlab]
|
188
|
+
|
189
|
+
if !github_conf.nil?
|
190
|
+
base_url = github_conf[:base_url] || ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
|
191
|
+
require 'modulesync/pr/github'
|
192
|
+
ModuleSync::PR::GitHub.new(github_conf[:token], base_url)
|
193
|
+
elsif !gitlab_conf.nil?
|
194
|
+
base_url = gitlab_conf[:base_url] || ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4')
|
195
|
+
require 'modulesync/pr/gitlab'
|
196
|
+
ModuleSync::PR::GitLab.new(gitlab_conf[:token], base_url)
|
197
|
+
elsif @pr.nil?
|
198
|
+
$stderr.puts 'No GitHub or GitLab token specified for --pr!'
|
199
|
+
raise
|
200
|
+
else
|
201
|
+
@pr
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.create_pr_manager
|
206
|
+
github_token = ENV.fetch('GITHUB_TOKEN', '')
|
207
|
+
gitlab_token = ENV.fetch('GITLAB_TOKEN', '')
|
208
|
+
|
209
|
+
if !github_token.empty?
|
210
|
+
require 'modulesync/pr/github'
|
211
|
+
ModuleSync::PR::GitHub.new(github_token, ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com'))
|
212
|
+
elsif !gitlab_token.empty?
|
213
|
+
require 'modulesync/pr/gitlab'
|
214
|
+
ModuleSync::PR::GitLab.new(gitlab_token, ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4'))
|
215
|
+
else
|
216
|
+
warn '--pr specified without environment variables GITHUB_TOKEN or GITLAB_TOKEN'
|
217
|
+
end
|
218
|
+
end
|
145
219
|
end
|
data/lib/modulesync/cli.rb
CHANGED
@@ -43,7 +43,8 @@ module ModuleSync
|
|
43
43
|
:default => CLI.defaults[:git_base] || 'git@github.com:'
|
44
44
|
class_option :namespace,
|
45
45
|
:aliases => '-n',
|
46
|
-
:desc => 'Remote github namespace (user or organization) to clone from and push to.
|
46
|
+
:desc => 'Remote github namespace (user or organization) to clone from and push to.' \
|
47
|
+
' Defaults to puppetlabs',
|
47
48
|
:default => CLI.defaults[:namespace] || 'puppetlabs'
|
48
49
|
class_option :filter,
|
49
50
|
:aliases => '-f',
|
@@ -53,7 +54,8 @@ module ModuleSync
|
|
53
54
|
:desc => 'A regular expression to skip repositories.'
|
54
55
|
class_option :branch,
|
55
56
|
:aliases => '-b',
|
56
|
-
:desc => 'Branch name to make the changes in.
|
57
|
+
:desc => 'Branch name to make the changes in.' \
|
58
|
+
' Defaults to the default branch of the upstream repository, but falls back to "master".',
|
57
59
|
:default => CLI.defaults[:branch]
|
58
60
|
|
59
61
|
desc 'update', 'Update the modules in managed_modules.yml'
|
@@ -63,7 +65,10 @@ module ModuleSync
|
|
63
65
|
:default => CLI.defaults[:message]
|
64
66
|
option :configs,
|
65
67
|
:aliases => '-c',
|
66
|
-
:desc => 'The local directory or remote repository to define the list of managed modules,
|
68
|
+
:desc => 'The local directory or remote repository to define the list of managed modules,' \
|
69
|
+
' the file templates, and the default values for template variables.'
|
70
|
+
option :managed_modules_conf,
|
71
|
+
:desc => 'The file name to define the list of managed modules'
|
67
72
|
option :remote_branch,
|
68
73
|
:aliases => '-r',
|
69
74
|
:desc => 'Remote branch name to push the changes to. Defaults to the branch name.',
|
@@ -85,6 +90,20 @@ module ModuleSync
|
|
85
90
|
:type => :boolean,
|
86
91
|
:desc => 'No-op mode',
|
87
92
|
:default => false
|
93
|
+
option :pr,
|
94
|
+
:type => :boolean,
|
95
|
+
:desc => 'Submit pull/merge request',
|
96
|
+
:default => false
|
97
|
+
option :pr_title,
|
98
|
+
:desc => 'Title of pull/merge request',
|
99
|
+
:default => CLI.defaults[:pr_title] || 'Update to module template files'
|
100
|
+
option :pr_labels,
|
101
|
+
:type => :array,
|
102
|
+
:desc => 'Labels to add to the pull/merge request',
|
103
|
+
:default => CLI.defaults[:pr_labels] || []
|
104
|
+
option :pr_target_branch,
|
105
|
+
:desc => 'Target branch for the pull/merge request',
|
106
|
+
:default => CLI.defaults[:pr_target_branch] || 'master'
|
88
107
|
option :offline,
|
89
108
|
:type => :boolean,
|
90
109
|
:desc => 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
|
@@ -104,18 +123,21 @@ module ModuleSync
|
|
104
123
|
option :tag_pattern,
|
105
124
|
:desc => 'The pattern to use when tagging releases.'
|
106
125
|
option :pre_commit_script,
|
107
|
-
:desc => 'A script to be run before
|
126
|
+
:desc => 'A script to be run before committing',
|
108
127
|
:default => CLI.defaults[:pre_commit_script]
|
109
128
|
option :fail_on_warnings,
|
110
129
|
:type => :boolean,
|
111
130
|
:aliases => '-F',
|
112
|
-
:desc => 'Produce a failure exit code when there are warnings
|
131
|
+
:desc => 'Produce a failure exit code when there are warnings' \
|
132
|
+
' (only has effect when --skip_broken is enabled)',
|
113
133
|
:default => false
|
114
134
|
|
115
135
|
def update
|
116
136
|
config = { :command => 'update' }.merge(options)
|
117
137
|
config = Util.symbolize_keys(config)
|
118
|
-
raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop]
|
138
|
+
raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop] \
|
139
|
+
|| config[:message] \
|
140
|
+
|| config[:offline]
|
119
141
|
config[:git_opts] = { 'amend' => config[:amend], 'force' => config[:force] }
|
120
142
|
ModuleSync.update(config)
|
121
143
|
end
|
data/lib/modulesync/git.rb
CHANGED
@@ -46,12 +46,13 @@ module ModuleSync
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.pull(git_base, name, branch, project_root, opts)
|
49
|
+
puts "Syncing #{name}"
|
49
50
|
Dir.mkdir(project_root) unless Dir.exist?(project_root)
|
50
51
|
|
51
52
|
# Repo needs to be cloned in the cwd
|
52
53
|
if !Dir.exist?("#{project_root}/#{name}") || !Dir.exist?("#{project_root}/#{name}/.git")
|
53
54
|
puts 'Cloning repository fresh'
|
54
|
-
remote = opts[:remote] || (git_base.start_with?('file://') ? "#{git_base}
|
55
|
+
remote = opts[:remote] || (git_base.start_with?('file://') ? "#{git_base}#{name}" : "#{git_base}#{name}.git")
|
55
56
|
local = "#{project_root}/#{name}"
|
56
57
|
puts "Cloning from #{remote}"
|
57
58
|
repo = ::Git.clone(remote, local)
|
@@ -151,11 +152,14 @@ module ModuleSync
|
|
151
152
|
rescue ::Git::GitExecuteError => git_error
|
152
153
|
if git_error.message =~ /working (directory|tree) clean/
|
153
154
|
puts "There were no files to update in #{name}. Not committing."
|
155
|
+
return false
|
154
156
|
else
|
155
157
|
puts git_error
|
156
158
|
raise
|
157
159
|
end
|
158
160
|
end
|
161
|
+
|
162
|
+
true
|
159
163
|
end
|
160
164
|
|
161
165
|
# Needed because of a bug in the git gem that lists ignored files as
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'modulesync/util'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module PR
|
6
|
+
# GitHub creates and manages pull requests on github.com or GitHub
|
7
|
+
# Enterprise installations.
|
8
|
+
class GitHub
|
9
|
+
def initialize(token, endpoint)
|
10
|
+
Octokit.configure do |c|
|
11
|
+
c.api_endpoint = endpoint
|
12
|
+
end
|
13
|
+
@api = Octokit::Client.new(:access_token => token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def manage(namespace, module_name, options)
|
17
|
+
repo_path = File.join(namespace, module_name)
|
18
|
+
head = "#{namespace}:#{options[:branch]}"
|
19
|
+
target_branch = options[:pr_target_branch] || 'master'
|
20
|
+
|
21
|
+
pull_requests = @api.pull_requests(repo_path, :state => 'open', :base => target_branch, :head => head)
|
22
|
+
if pull_requests.empty?
|
23
|
+
pr = @api.create_pull_request(repo_path,
|
24
|
+
target_branch,
|
25
|
+
options[:branch],
|
26
|
+
options[:pr_title],
|
27
|
+
options[:message])
|
28
|
+
$stdout.puts \
|
29
|
+
"Submitted PR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into #{target_branch}"
|
30
|
+
else
|
31
|
+
# Skip creating the PR if it exists already.
|
32
|
+
$stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{options[:branch]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# PR labels can either be a list in the YAML file or they can pass in a comma
|
36
|
+
# separated list via the command line argument.
|
37
|
+
pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
|
38
|
+
|
39
|
+
# We only assign labels to the PR if we've discovered a list > 1. The labels MUST
|
40
|
+
# already exist. We DO NOT create missing labels.
|
41
|
+
return if pr_labels.empty?
|
42
|
+
$stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
|
43
|
+
@api.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'gitlab'
|
2
|
+
require 'modulesync/util'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module PR
|
6
|
+
# GitLab creates and manages merge requests on gitlab.com or private GitLab
|
7
|
+
# installations.
|
8
|
+
class GitLab
|
9
|
+
def initialize(token, endpoint)
|
10
|
+
@api = Gitlab::Client.new(
|
11
|
+
:endpoint => endpoint,
|
12
|
+
:private_token => token
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def manage(namespace, module_name, options)
|
17
|
+
repo_path = File.join(namespace, module_name)
|
18
|
+
|
19
|
+
head = "#{namespace}:#{options[:branch]}"
|
20
|
+
target_branch = options[:pr_target_branch] || 'master'
|
21
|
+
merge_requests = @api.merge_requests(repo_path,
|
22
|
+
:state => 'opened',
|
23
|
+
:source_branch => head,
|
24
|
+
:target_branch => target_branch)
|
25
|
+
if merge_requests.empty?
|
26
|
+
mr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
|
27
|
+
mr = @api.create_merge_request(repo_path, options[:pr_title],
|
28
|
+
:source_branch => options[:branch],
|
29
|
+
:target_branch => target_branch,
|
30
|
+
:labels => mr_labels)
|
31
|
+
$stdout.puts \
|
32
|
+
"Submitted MR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into #{target_branch}"
|
33
|
+
return if mr_labels.empty?
|
34
|
+
$stdout.puts "Attached the following labels to MR #{mr.iid}: #{mr_labels.join(', ')}"
|
35
|
+
else
|
36
|
+
# Skip creating the MR if it exists already.
|
37
|
+
$stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{options[:branch]}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/modulesync/renderer.rb
CHANGED
@@ -4,8 +4,9 @@ require 'find'
|
|
4
4
|
module ModuleSync
|
5
5
|
module Renderer
|
6
6
|
class ForgeModuleFile
|
7
|
-
def initialize(configs = {})
|
7
|
+
def initialize(configs = {}, metadata = {})
|
8
8
|
@configs = configs
|
9
|
+
@metadata = metadata
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
@@ -26,8 +27,8 @@ module ModuleSync
|
|
26
27
|
File.delete(file) if File.exist?(file)
|
27
28
|
end
|
28
29
|
|
29
|
-
def self.render(_template, configs = {})
|
30
|
-
ForgeModuleFile.new(configs).render
|
30
|
+
def self.render(_template, configs = {}, metadata = {})
|
31
|
+
ForgeModuleFile.new(configs, metadata).render
|
31
32
|
end
|
32
33
|
|
33
34
|
def self.sync(template, target_name)
|
data/lib/modulesync/settings.rb
CHANGED
@@ -19,10 +19,9 @@ module ModuleSync
|
|
19
19
|
|
20
20
|
def build_file_configs(target_name)
|
21
21
|
file_def = lookup_config(defaults, target_name)
|
22
|
-
file_md = lookup_config(module_defaults, target_name)
|
23
22
|
file_mc = lookup_config(module_configs, target_name)
|
24
23
|
|
25
|
-
global_defaults.merge(file_def).merge(
|
24
|
+
global_defaults.merge(file_def).merge(module_defaults).merge(file_mc).merge(additional_settings)
|
26
25
|
end
|
27
26
|
|
28
27
|
def managed?(target_name)
|