simplygenius-atmos 0.7.1 → 0.8.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/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
|