simplygenius-atmos 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/exe/atmos +2 -2
- data/lib/{atmos.rb → simplygenius/atmos.rb} +9 -7
- data/lib/simplygenius/atmos/cli.rb +116 -0
- data/lib/simplygenius/atmos/commands/account.rb +69 -0
- data/lib/simplygenius/atmos/commands/apply.rb +24 -0
- data/lib/simplygenius/atmos/commands/auth_exec.rb +34 -0
- data/lib/simplygenius/atmos/commands/base_command.rb +16 -0
- data/lib/simplygenius/atmos/commands/bootstrap.rb +76 -0
- data/lib/simplygenius/atmos/commands/container.rb +62 -0
- data/lib/simplygenius/atmos/commands/destroy.rb +22 -0
- data/lib/simplygenius/atmos/commands/generate.rb +187 -0
- data/lib/simplygenius/atmos/commands/init.rb +22 -0
- data/lib/simplygenius/atmos/commands/new.rb +22 -0
- data/lib/simplygenius/atmos/commands/otp.rb +58 -0
- data/lib/simplygenius/atmos/commands/plan.rb +24 -0
- data/lib/simplygenius/atmos/commands/secret.rb +91 -0
- data/lib/simplygenius/atmos/commands/terraform.rb +56 -0
- data/lib/simplygenius/atmos/commands/user.rb +78 -0
- data/lib/simplygenius/atmos/config.rb +279 -0
- data/lib/simplygenius/atmos/exceptions.rb +13 -0
- data/lib/simplygenius/atmos/generator.rb +232 -0
- data/lib/simplygenius/atmos/ipc.rb +136 -0
- data/lib/simplygenius/atmos/ipc_actions/notify.rb +31 -0
- data/lib/simplygenius/atmos/ipc_actions/ping.rb +23 -0
- data/lib/simplygenius/atmos/logging.rb +164 -0
- data/lib/simplygenius/atmos/otp.rb +62 -0
- data/lib/simplygenius/atmos/plugin.rb +27 -0
- data/lib/simplygenius/atmos/plugin_manager.rb +120 -0
- data/lib/simplygenius/atmos/plugins/output_filter.rb +29 -0
- data/lib/simplygenius/atmos/plugins/prompt_notify.rb +21 -0
- data/lib/simplygenius/atmos/provider_factory.rb +23 -0
- data/lib/simplygenius/atmos/providers/aws/account_manager.rb +83 -0
- data/lib/simplygenius/atmos/providers/aws/auth_manager.rb +220 -0
- data/lib/simplygenius/atmos/providers/aws/container_manager.rb +118 -0
- data/lib/simplygenius/atmos/providers/aws/provider.rb +53 -0
- data/lib/simplygenius/atmos/providers/aws/s3_secret_manager.rb +51 -0
- data/lib/simplygenius/atmos/providers/aws/user_manager.rb +213 -0
- data/lib/simplygenius/atmos/settings_hash.rb +93 -0
- data/lib/simplygenius/atmos/source_path.rb +186 -0
- data/lib/simplygenius/atmos/template.rb +117 -0
- data/lib/simplygenius/atmos/terraform_executor.rb +297 -0
- data/lib/simplygenius/atmos/ui.rb +173 -0
- data/lib/simplygenius/atmos/utils.rb +54 -0
- data/lib/simplygenius/atmos/version.rb +5 -0
- data/templates/new/config/atmos.yml +21 -13
- data/templates/new/config/atmos/recipes.yml +16 -0
- data/templates/new/config/atmos/runtime.yml +9 -0
- metadata +46 -40
- data/lib/atmos/cli.rb +0 -105
- data/lib/atmos/commands/account.rb +0 -65
- data/lib/atmos/commands/apply.rb +0 -20
- data/lib/atmos/commands/auth_exec.rb +0 -29
- data/lib/atmos/commands/base_command.rb +0 -12
- data/lib/atmos/commands/bootstrap.rb +0 -72
- data/lib/atmos/commands/container.rb +0 -58
- data/lib/atmos/commands/destroy.rb +0 -18
- data/lib/atmos/commands/generate.rb +0 -90
- data/lib/atmos/commands/init.rb +0 -18
- data/lib/atmos/commands/new.rb +0 -18
- data/lib/atmos/commands/otp.rb +0 -54
- data/lib/atmos/commands/plan.rb +0 -20
- data/lib/atmos/commands/secret.rb +0 -87
- data/lib/atmos/commands/terraform.rb +0 -52
- data/lib/atmos/commands/user.rb +0 -74
- data/lib/atmos/config.rb +0 -208
- data/lib/atmos/exceptions.rb +0 -9
- data/lib/atmos/generator.rb +0 -199
- data/lib/atmos/generator_factory.rb +0 -93
- data/lib/atmos/ipc.rb +0 -132
- data/lib/atmos/ipc_actions/notify.rb +0 -27
- data/lib/atmos/ipc_actions/ping.rb +0 -19
- data/lib/atmos/logging.rb +0 -160
- data/lib/atmos/otp.rb +0 -61
- data/lib/atmos/provider_factory.rb +0 -19
- data/lib/atmos/providers/aws/account_manager.rb +0 -82
- data/lib/atmos/providers/aws/auth_manager.rb +0 -208
- data/lib/atmos/providers/aws/container_manager.rb +0 -116
- data/lib/atmos/providers/aws/provider.rb +0 -51
- data/lib/atmos/providers/aws/s3_secret_manager.rb +0 -49
- data/lib/atmos/providers/aws/user_manager.rb +0 -211
- data/lib/atmos/settings_hash.rb +0 -90
- data/lib/atmos/terraform_executor.rb +0 -267
- data/lib/atmos/ui.rb +0 -159
- data/lib/atmos/utils.rb +0 -50
- data/lib/atmos/version.rb +0 -3
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require 'climate_control'
|
3
|
+
|
4
|
+
module SimplyGenius
|
5
|
+
module Atmos
|
6
|
+
module Commands
|
7
|
+
|
8
|
+
class User < BaseCommand
|
9
|
+
|
10
|
+
def self.description
|
11
|
+
"Manages users in the cloud provider"
|
12
|
+
end
|
13
|
+
|
14
|
+
subcommand "create", "Create a new user" do
|
15
|
+
|
16
|
+
option ["-f", "--force"],
|
17
|
+
:flag, "forces deletion/updates for pre-existing resources\n",
|
18
|
+
default: false
|
19
|
+
|
20
|
+
option ["-l", "--login"],
|
21
|
+
:flag, "generate a login password\n",
|
22
|
+
default: false
|
23
|
+
|
24
|
+
option ["-m", "--mfa"],
|
25
|
+
:flag, "setup a mfa device\n",
|
26
|
+
default: false
|
27
|
+
|
28
|
+
option ["-k", "--key"],
|
29
|
+
:flag, "create access keys\n",
|
30
|
+
default: false
|
31
|
+
|
32
|
+
option ["-p", "--public-key"],
|
33
|
+
"PUBLIC_KEY", "add ssh public key\n"
|
34
|
+
|
35
|
+
option ["-g", "--group"],
|
36
|
+
"GROUP",
|
37
|
+
"associate the given groups to new user\n",
|
38
|
+
multivalued: true
|
39
|
+
|
40
|
+
parameter "USERNAME",
|
41
|
+
"The username of the user to add\nShould be an email address" do |u|
|
42
|
+
raise ArgumentError.new("Not an email") if u !~ URI::MailTo::EMAIL_REGEXP
|
43
|
+
u
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
|
48
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
49
|
+
ClimateControl.modify(auth_env) do
|
50
|
+
manager = Atmos.config.provider.user_manager
|
51
|
+
user = manager.create_user(username)
|
52
|
+
user.merge!(manager.set_groups(username, group_list, force: force?)) if group_list.present?
|
53
|
+
user.merge!(manager.enable_login(username, force: force?)) if login?
|
54
|
+
user.merge!(manager.enable_mfa(username, force: force?)) if mfa?
|
55
|
+
user.merge!(manager.enable_access_keys(username, force: force?)) if key?
|
56
|
+
user.merge!(manager.set_public_key(username, public_key, force: force?)) if public_key.present?
|
57
|
+
|
58
|
+
logger.info "\nUser created:\n#{display user}\n"
|
59
|
+
|
60
|
+
if mfa? && user[:mfa_secret]
|
61
|
+
save_mfa = agree("Save the MFA secret for runtime integration with auth? ") {|q|
|
62
|
+
q.default = 'y'
|
63
|
+
}
|
64
|
+
Atmos::Otp.instance.save if save_mfa
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
require_relative '../atmos/settings_hash'
|
3
|
+
require_relative '../atmos/provider_factory'
|
4
|
+
require_relative '../atmos/plugin_manager'
|
5
|
+
require 'yaml'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'find'
|
8
|
+
|
9
|
+
module SimplyGenius
|
10
|
+
module Atmos
|
11
|
+
|
12
|
+
class Config
|
13
|
+
include GemLogger::LoggerSupport
|
14
|
+
include FileUtils
|
15
|
+
|
16
|
+
attr_accessor :atmos_env, :root_dir,
|
17
|
+
:config_file,
|
18
|
+
:tmp_root
|
19
|
+
|
20
|
+
def initialize(atmos_env)
|
21
|
+
@atmos_env = atmos_env
|
22
|
+
@root_dir = File.expand_path(Dir.pwd)
|
23
|
+
@config_file = File.join(root_dir, "config", "atmos.yml")
|
24
|
+
@tmp_root = File.join(root_dir, "tmp")
|
25
|
+
@included_configs = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_atmos_repo?
|
29
|
+
File.exist?(config_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
load
|
34
|
+
result = @config.notation_get(key)
|
35
|
+
return result
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
load
|
40
|
+
@config.to_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def provider
|
44
|
+
@provider ||= ProviderFactory.get(self[:provider])
|
45
|
+
end
|
46
|
+
|
47
|
+
def plugin_manager
|
48
|
+
@plugin_manager ||= PluginManager.new(self[:plugins])
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_env_names
|
52
|
+
load
|
53
|
+
@full_config[:environments].keys
|
54
|
+
end
|
55
|
+
|
56
|
+
def account_hash
|
57
|
+
load
|
58
|
+
environments = @full_config[:environments] || {}
|
59
|
+
environments.inject(Hash.new) do |accum, entry|
|
60
|
+
accum[entry.first] = entry.last[:account_id]
|
61
|
+
accum
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def tmp_dir
|
66
|
+
@tmp_dir ||= begin
|
67
|
+
dir = File.join(tmp_root, atmos_env)
|
68
|
+
logger.debug("Tmp dir: #{dir}")
|
69
|
+
mkdir_p(dir)
|
70
|
+
dir
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def auth_cache_dir
|
75
|
+
@auth_cache_dir ||= begin
|
76
|
+
dir = File.join(tmp_dir, 'auth')
|
77
|
+
logger.debug("Auth cache dir: #{dir}")
|
78
|
+
mkdir_p(dir)
|
79
|
+
dir
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def tf_working_dir(group='default')
|
84
|
+
@tf_working_dir ||= {}
|
85
|
+
@tf_working_dir[group] ||= begin
|
86
|
+
dir = File.join(tmp_dir, 'tf', group)
|
87
|
+
logger.debug("Terraform working dir: #{dir}")
|
88
|
+
mkdir_p(dir)
|
89
|
+
dir
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_user_load_path(*paths)
|
94
|
+
load_path = paths + Array(self[:load_path])
|
95
|
+
if load_path.present?
|
96
|
+
load_path = load_path.collect { |path| File.expand_path(path) }
|
97
|
+
logger.debug("Adding to load path: #{load_path.inspect}")
|
98
|
+
$LOAD_PATH.insert(0, *load_path)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
INTERP_PATTERN = /(\#\{([^\}]+)\})/
|
105
|
+
|
106
|
+
def config_merge(lhs, rhs)
|
107
|
+
result = nil
|
108
|
+
|
109
|
+
return rhs if lhs.nil?
|
110
|
+
return lhs if rhs.nil?
|
111
|
+
|
112
|
+
# Warn if user fat fingered config
|
113
|
+
unless lhs.is_a?(rhs.class) || rhs.is_a?(lhs.class)
|
114
|
+
logger.warn("Different types in deep merge: #{lhs.class}, #{rhs.class}")
|
115
|
+
end
|
116
|
+
|
117
|
+
case rhs
|
118
|
+
when Hash
|
119
|
+
result = lhs.deep_dup
|
120
|
+
|
121
|
+
lhs.each do |k, v|
|
122
|
+
if k =~ /^\^(.*)/
|
123
|
+
key = k.is_a?(Symbol) ? $1.to_sym : $1
|
124
|
+
result[key] = result.delete(k)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
rhs.each do |k, v|
|
129
|
+
if k =~ /^\^(.*)/
|
130
|
+
key = k.is_a?(Symbol) ? $1.to_sym : $1
|
131
|
+
result[key] = v
|
132
|
+
else
|
133
|
+
result[k] = config_merge(result[k], v)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
when Enumerable
|
137
|
+
result = lhs + rhs
|
138
|
+
else
|
139
|
+
result = rhs
|
140
|
+
end
|
141
|
+
|
142
|
+
return result
|
143
|
+
end
|
144
|
+
|
145
|
+
def load_config_sources(relative_root, config, *patterns)
|
146
|
+
patterns.each do |pattern|
|
147
|
+
logger.debug("Loading atmos config files using pattern: #{pattern}")
|
148
|
+
|
149
|
+
# relative to main atmos config file unless qualified
|
150
|
+
if pattern !~ /^[\/~]/
|
151
|
+
pattern = File.join(relative_root, pattern)
|
152
|
+
end
|
153
|
+
# expand to handle tilde/etc
|
154
|
+
pattern = File.expand_path(pattern)
|
155
|
+
logger.debug("Expanded pattern: #{pattern}")
|
156
|
+
|
157
|
+
Dir[pattern].each do |f|
|
158
|
+
logger.debug("Loading atmos config file: #{f}")
|
159
|
+
h = SettingsHash.new(YAML.load_file(f))
|
160
|
+
config = config_merge(config, h)
|
161
|
+
@included_configs << f
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
config
|
166
|
+
end
|
167
|
+
|
168
|
+
def load_submap(relative_root, group, name, config)
|
169
|
+
submap_dir = File.join(relative_root, 'atmos', group)
|
170
|
+
submap_file = File.join(submap_dir, "#{name}.yml")
|
171
|
+
if File.exist?(submap_file)
|
172
|
+
logger.debug("Loading atmos #{group} config file: #{submap_file}")
|
173
|
+
h = SettingsHash.new({group => {name => YAML.load_file(submap_file)}})
|
174
|
+
config = config_merge(config, h)
|
175
|
+
@included_configs << submap_file
|
176
|
+
end
|
177
|
+
|
178
|
+
begin
|
179
|
+
submap = config.deep_fetch(group, name)
|
180
|
+
config = config_merge(config, submap)
|
181
|
+
rescue
|
182
|
+
logger.debug("No #{group} config found for '#{name}'")
|
183
|
+
end
|
184
|
+
|
185
|
+
config
|
186
|
+
end
|
187
|
+
|
188
|
+
def load
|
189
|
+
@config ||= begin
|
190
|
+
|
191
|
+
logger.debug("Atmos env: #{atmos_env}")
|
192
|
+
|
193
|
+
if ! File.exist?(config_file)
|
194
|
+
logger.warn "Could not find an atmos config file at: #{config_file}"
|
195
|
+
@full_config = SettingsHash.new
|
196
|
+
else
|
197
|
+
logger.debug("Loading atmos config file #{config_file}")
|
198
|
+
@full_config = SettingsHash.new(YAML.load_file(config_file))
|
199
|
+
@included_configs << config_file
|
200
|
+
end
|
201
|
+
|
202
|
+
@full_config = load_config_sources(File.dirname(config_file), @full_config, *Array(@full_config[:config_sources]))
|
203
|
+
|
204
|
+
@full_config['provider'] = provider_name = @full_config['provider'] || 'aws'
|
205
|
+
|
206
|
+
@full_config = load_submap(File.dirname(config_file), 'providers', provider_name, @full_config)
|
207
|
+
@full_config = load_submap(File.dirname(config_file), 'environments', atmos_env, @full_config)
|
208
|
+
|
209
|
+
global = SettingsHash.new(@full_config.reject {|k, v| ['providers', 'environments'].include?(k) })
|
210
|
+
conf = config_merge(global, {
|
211
|
+
atmos_env: atmos_env,
|
212
|
+
atmos_version: VERSION
|
213
|
+
})
|
214
|
+
expand(conf, conf)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def expand(config, obj)
|
219
|
+
case obj
|
220
|
+
when Hash
|
221
|
+
SettingsHash.new(Hash[obj.collect {|k, v| [k, expand(config, v)] }])
|
222
|
+
when Array
|
223
|
+
result = obj.collect {|i| expand(config, i) }
|
224
|
+
# HACK: accounting for the case when someone wants to force an override using '^' as the first list item, when
|
225
|
+
# there is no upstream to override (i.e. merge proc doesn't get triggered as key is unique, so just added verbatim)
|
226
|
+
result.delete_at(0) if result[0] == "^"
|
227
|
+
result
|
228
|
+
when String
|
229
|
+
result = obj
|
230
|
+
result.scan(INTERP_PATTERN).each do |substr, statement|
|
231
|
+
# TODO: check for cycles
|
232
|
+
if statement =~ /^[\w\.\[\]]$/
|
233
|
+
val = config.notation_get(statement)
|
234
|
+
else
|
235
|
+
# TODO: be consistent with dot notation between eval and
|
236
|
+
# notation_get. eval ends up calling Hashie method_missing,
|
237
|
+
# which returns nil if a key doesn't exist, causing a nil
|
238
|
+
# exception for next item in chain, while notation_get returns
|
239
|
+
# nil gracefully for the entire chain (preferred)
|
240
|
+
begin
|
241
|
+
val = eval(statement, config.instance_eval("binding"))
|
242
|
+
rescue => e
|
243
|
+
file, line = find_config_error(substr)
|
244
|
+
file_msg = file.nil? ? "" : " in #{File.basename(file)}:#{line}"
|
245
|
+
raise RuntimeError.new("Failing config statement '#{substr}'#{file_msg} => #{e.class} #{e.message}")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
result = result.sub(substr, expand(config, val).to_s)
|
249
|
+
end
|
250
|
+
result = true if result == 'true'
|
251
|
+
result = false if result == 'false'
|
252
|
+
result
|
253
|
+
else
|
254
|
+
obj
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def find_config_error(statement)
|
259
|
+
filename = nil
|
260
|
+
line = 0
|
261
|
+
|
262
|
+
@included_configs.each do |c|
|
263
|
+
current_line = 0
|
264
|
+
File.foreach(c) do |f|
|
265
|
+
current_line += 1
|
266
|
+
if f.include?(statement)
|
267
|
+
filename = c
|
268
|
+
line = current_line
|
269
|
+
break
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
return filename, line
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
require_relative '../atmos/ui'
|
3
|
+
require 'thor'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
module SimplyGenius
|
7
|
+
module Atmos
|
8
|
+
|
9
|
+
# From https://github.com/rubber/rubber/blob/master/lib/rubber/commands/vulcanize.rb
|
10
|
+
class Generator
|
11
|
+
|
12
|
+
include GemLogger::LoggerSupport
|
13
|
+
|
14
|
+
attr_reader :visited_templates
|
15
|
+
|
16
|
+
def initialize(**opts)
|
17
|
+
if opts.has_key?(:dependencies)
|
18
|
+
@dependencies = opts.delete(:dependencies)
|
19
|
+
else
|
20
|
+
@dependencies = true
|
21
|
+
end
|
22
|
+
@thor_opts = opts
|
23
|
+
@thor_generators = {}
|
24
|
+
@resolved_templates = {}
|
25
|
+
@visited_templates = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate(*template_names, context: {})
|
29
|
+
seen = Set.new
|
30
|
+
|
31
|
+
template_names.each do |template_name|
|
32
|
+
|
33
|
+
# clone since we are mutating context and can be called from within a
|
34
|
+
# template, walk_deps also clones
|
35
|
+
tmpl = SourcePath.find_template(template_name)
|
36
|
+
tmpl.clone.context.merge!(context)
|
37
|
+
|
38
|
+
if @dependencies
|
39
|
+
deps = tmpl.walk_dependencies.to_a
|
40
|
+
else
|
41
|
+
deps = [tmpl]
|
42
|
+
end
|
43
|
+
|
44
|
+
deps.each do |dep_tmpl|
|
45
|
+
seen_tmpl = [dep_tmpl.name, dep_tmpl.scoped_context]
|
46
|
+
unless seen.include?(seen_tmpl)
|
47
|
+
apply_template(dep_tmpl)
|
48
|
+
end
|
49
|
+
seen << seen_tmpl
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
return visited_templates
|
55
|
+
end
|
56
|
+
|
57
|
+
def apply_template(tmpl)
|
58
|
+
@thor_generators[tmpl.source] ||= Class.new(ThorGenerator) do
|
59
|
+
source_root tmpl.source.directory
|
60
|
+
end
|
61
|
+
|
62
|
+
gen = @thor_generators[tmpl.source].new(tmpl, self, **@thor_opts)
|
63
|
+
gen.apply
|
64
|
+
visited_templates << tmpl
|
65
|
+
|
66
|
+
gen # makes testing easier by giving a handle to thor generator instance
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
class ThorGenerator < Thor
|
72
|
+
|
73
|
+
include Thor::Actions
|
74
|
+
attr_reader :tmpl, :parent
|
75
|
+
|
76
|
+
def initialize(tmpl, parent, **opts)
|
77
|
+
@tmpl = tmpl
|
78
|
+
@parent = parent
|
79
|
+
super([], **opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
no_commands do
|
83
|
+
|
84
|
+
include GemLogger::LoggerSupport
|
85
|
+
include UI
|
86
|
+
|
87
|
+
def apply
|
88
|
+
template_dir = tmpl.directory
|
89
|
+
path = tmpl.source.directory
|
90
|
+
|
91
|
+
logger.debug("Applying template '#{tmpl.name}' from '#{template_dir}' in sourcepath '#{path}'")
|
92
|
+
|
93
|
+
Find.find(template_dir) do |f|
|
94
|
+
next if f == template_dir # don't create a directory for the template dir itself, but don't prune so we recurse
|
95
|
+
Find.prune if f == tmpl.config_path # don't copy over templates.yml
|
96
|
+
Find.prune if f == tmpl.actions_path # don't copy over templates.rb
|
97
|
+
|
98
|
+
# Using File.join(x, '') to ensure trailing slash to make sure we end
|
99
|
+
# up with a relative path
|
100
|
+
template_rel = f.gsub(/#{File.join(template_dir, '')}/, '')
|
101
|
+
source_rel = f.gsub(/#{File.join(path, '')}/, '')
|
102
|
+
dest_rel = source_rel.gsub(/^#{File.join(tmpl.name, '')}/, '')
|
103
|
+
|
104
|
+
# Only include optional files when their conditions eval to true
|
105
|
+
optional = tmpl.optional[template_rel]
|
106
|
+
if optional
|
107
|
+
exclude = ! eval(optional)
|
108
|
+
logger.debug("Optional template '#{template_rel}' with condition: '#{optional}', excluding=#{exclude}")
|
109
|
+
Find.prune if exclude
|
110
|
+
end
|
111
|
+
|
112
|
+
logger.debug("Template '#{source_rel}' => '#{dest_rel}'")
|
113
|
+
if File.directory?(f)
|
114
|
+
empty_directory(dest_rel)
|
115
|
+
else
|
116
|
+
copy_file(source_rel, dest_rel, mode: :preserve)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
eval tmpl.actions, binding, tmpl.actions_path
|
121
|
+
end
|
122
|
+
|
123
|
+
def context
|
124
|
+
tmpl.context
|
125
|
+
end
|
126
|
+
|
127
|
+
def scoped_context
|
128
|
+
tmpl.scoped_context
|
129
|
+
end
|
130
|
+
|
131
|
+
def lookup_context(varname)
|
132
|
+
varname.blank? ? nil: tmpl.scoped_context[varname]
|
133
|
+
end
|
134
|
+
|
135
|
+
def track_context(varname, value)
|
136
|
+
varname.blank? || value.nil? ? nil: tmpl.scoped_context[varname] = value
|
137
|
+
end
|
138
|
+
|
139
|
+
def respond_to_missing?(method_name, *args)
|
140
|
+
scoped_context.respond_to_missing?(method_name, *args)
|
141
|
+
end
|
142
|
+
|
143
|
+
def method_missing(method_name, *args, &blk)
|
144
|
+
scoped_context.method_missing(method_name, *args, &blk)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
desc "ask <question string> [varname: name]", "Asks a question, allowing context to provide answer using varname"
|
150
|
+
def ask(question, answer_type = nil, varname: nil, &details)
|
151
|
+
result = lookup_context(varname)
|
152
|
+
if result.nil?
|
153
|
+
result = super(question, answer_type, &details)
|
154
|
+
end
|
155
|
+
track_context(varname, result)
|
156
|
+
result
|
157
|
+
end
|
158
|
+
|
159
|
+
desc "agree <question string> [varname: name]", "Asks a Y/N question, allowing context to provide answer using varname"
|
160
|
+
def agree(question, character = nil, varname: nil, &details)
|
161
|
+
result = lookup_context(varname)
|
162
|
+
if result.nil?
|
163
|
+
result = super(question, character, &details)
|
164
|
+
end
|
165
|
+
result = !!result
|
166
|
+
track_context(varname, result)
|
167
|
+
result
|
168
|
+
end
|
169
|
+
|
170
|
+
desc "choose menu_block [varname: name]", "Provides a menu with choices, allowing context to provide answer using varname"
|
171
|
+
def choose(*items, varname: nil, &details)
|
172
|
+
result = lookup_context(varname)
|
173
|
+
if result.nil?
|
174
|
+
result = super(*items, &details)
|
175
|
+
end
|
176
|
+
track_context(varname, result)
|
177
|
+
result
|
178
|
+
end
|
179
|
+
|
180
|
+
desc "generate <tmpl_name> [context_hash]", "Generates the given template with optional context"
|
181
|
+
def generate(name, ctx: context)
|
182
|
+
parent.generate(name, context: ctx.clone)
|
183
|
+
end
|
184
|
+
|
185
|
+
desc "raw_config <yml_filename>", "Loads yml file"
|
186
|
+
def raw_config(yml_file)
|
187
|
+
@raw_configs ||= {}
|
188
|
+
@raw_configs[yml_file] ||= SettingsHash.new((YAML.load_file(yml_file) rescue {}))
|
189
|
+
end
|
190
|
+
|
191
|
+
desc "add_config <yml_filename> <key> <value> [additive: true]", "Adds config to yml file if not there (additive=true)"
|
192
|
+
def add_config(yml_file, key, value, additive: true)
|
193
|
+
new_yml = SettingsHash.add_config(yml_file, key, value, additive: additive)
|
194
|
+
create_file yml_file, new_yml
|
195
|
+
@raw_configs.delete(yml_file) if @raw_configs
|
196
|
+
end
|
197
|
+
|
198
|
+
desc "get_config <yml_filename> <key>", "Gets value of key (dot-notation for nesting) from yml file"
|
199
|
+
def get_config(yml_file, key)
|
200
|
+
config = raw_config(yml_file)
|
201
|
+
config.notation_get(key)
|
202
|
+
end
|
203
|
+
|
204
|
+
desc "config_present? <yml_filename> <key> <value>", "Tests if key is in yml file and equal to value if supplied"
|
205
|
+
def config_present?(yml_file, key, value=nil)
|
206
|
+
val = get_config(yml_file, key)
|
207
|
+
|
208
|
+
result = val.present?
|
209
|
+
if value && result
|
210
|
+
if val.is_a?(Array)
|
211
|
+
result = Array(value).all? {|v| val.include?(v) }
|
212
|
+
else
|
213
|
+
result = (val == value)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
return result
|
218
|
+
end
|
219
|
+
|
220
|
+
desc "new_keys? <src_yml_filename> <dest_yml_filename>", "Tests if src yml has top level keys not present in dest yml"
|
221
|
+
def new_keys?(src_yml_file, dest_yml_file)
|
222
|
+
src = raw_config(src_yml_file).keys.sort
|
223
|
+
dest = raw_config(dest_yml_file).keys.sort
|
224
|
+
(src - dest).size > 0
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|