modulesync 0.9.0 → 1.3.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/.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)
|