simplygenius-atmos 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +212 -0
- data/exe/atmos +4 -0
- data/exe/atmos-docker +12 -0
- data/lib/atmos.rb +12 -0
- data/lib/atmos/cli.rb +105 -0
- data/lib/atmos/commands/account.rb +65 -0
- data/lib/atmos/commands/apply.rb +20 -0
- data/lib/atmos/commands/auth_exec.rb +29 -0
- data/lib/atmos/commands/base_command.rb +12 -0
- data/lib/atmos/commands/bootstrap.rb +72 -0
- data/lib/atmos/commands/container.rb +58 -0
- data/lib/atmos/commands/destroy.rb +18 -0
- data/lib/atmos/commands/generate.rb +90 -0
- data/lib/atmos/commands/init.rb +18 -0
- data/lib/atmos/commands/new.rb +18 -0
- data/lib/atmos/commands/otp.rb +54 -0
- data/lib/atmos/commands/plan.rb +20 -0
- data/lib/atmos/commands/secret.rb +87 -0
- data/lib/atmos/commands/terraform.rb +52 -0
- data/lib/atmos/commands/user.rb +74 -0
- data/lib/atmos/config.rb +208 -0
- data/lib/atmos/exceptions.rb +9 -0
- data/lib/atmos/generator.rb +199 -0
- data/lib/atmos/generator_factory.rb +93 -0
- data/lib/atmos/ipc.rb +132 -0
- data/lib/atmos/ipc_actions/notify.rb +27 -0
- data/lib/atmos/ipc_actions/ping.rb +19 -0
- data/lib/atmos/logging.rb +160 -0
- data/lib/atmos/otp.rb +61 -0
- data/lib/atmos/provider_factory.rb +19 -0
- data/lib/atmos/providers/aws/account_manager.rb +82 -0
- data/lib/atmos/providers/aws/auth_manager.rb +208 -0
- data/lib/atmos/providers/aws/container_manager.rb +116 -0
- data/lib/atmos/providers/aws/provider.rb +51 -0
- data/lib/atmos/providers/aws/s3_secret_manager.rb +49 -0
- data/lib/atmos/providers/aws/user_manager.rb +211 -0
- data/lib/atmos/settings_hash.rb +90 -0
- data/lib/atmos/terraform_executor.rb +267 -0
- data/lib/atmos/ui.rb +159 -0
- data/lib/atmos/utils.rb +50 -0
- data/lib/atmos/version.rb +3 -0
- data/templates/new/config/atmos.yml +50 -0
- data/templates/new/config/atmos/runtime.yml +43 -0
- data/templates/new/templates.yml +1 -0
- metadata +526 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require_relative '../../atmos/terraform_executor'
|
3
|
+
|
4
|
+
module Atmos::Commands
|
5
|
+
|
6
|
+
class Terraform < BaseCommand
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Runs terraform"
|
10
|
+
end
|
11
|
+
|
12
|
+
# override so we can pass all options/flags/parameters directly to
|
13
|
+
# terraform instead of having clamp parse them
|
14
|
+
def parse(arguments)
|
15
|
+
@terraform_arguments = arguments
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute
|
19
|
+
|
20
|
+
unless Atmos.config.is_atmos_repo?
|
21
|
+
signal_usage_error <<~EOF
|
22
|
+
Atmos can only run terraform from a location configured for atmos.
|
23
|
+
Have you run atmos init?"
|
24
|
+
EOF
|
25
|
+
end
|
26
|
+
|
27
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
28
|
+
begin
|
29
|
+
|
30
|
+
# TODO: hack to allow apply/etc for bootstrap group
|
31
|
+
# Fix this once we allow more extensive recipe grouping
|
32
|
+
working_group = 'default'
|
33
|
+
@terraform_arguments.each_with_index do |a, i|
|
34
|
+
if a == "--group"
|
35
|
+
@terraform_arguments.delete_at(i)
|
36
|
+
working_group = @terraform_arguments.delete_at(i)
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
exe = Atmos::TerraformExecutor.new(process_env: auth_env, working_group: working_group)
|
42
|
+
get_modules = @terraform_arguments.delete("--get-modules")
|
43
|
+
exe.run(*@terraform_arguments, get_modules: get_modules.present?)
|
44
|
+
rescue Atmos::TerraformExecutor::ProcessFailed => e
|
45
|
+
logger.error(e.message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require 'climate_control'
|
3
|
+
|
4
|
+
module Atmos::Commands
|
5
|
+
|
6
|
+
class User < BaseCommand
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Manages users in the cloud provider"
|
10
|
+
end
|
11
|
+
|
12
|
+
subcommand "create", "Create a new user" do
|
13
|
+
|
14
|
+
option ["-f", "--force"],
|
15
|
+
:flag, "forces deletion/updates for pre-existing resources\n",
|
16
|
+
default: false
|
17
|
+
|
18
|
+
option ["-l", "--login"],
|
19
|
+
:flag, "generate a login password\n",
|
20
|
+
default: false
|
21
|
+
|
22
|
+
option ["-m", "--mfa"],
|
23
|
+
:flag, "setup a mfa device\n",
|
24
|
+
default: false
|
25
|
+
|
26
|
+
option ["-k", "--key"],
|
27
|
+
:flag, "create access keys\n",
|
28
|
+
default: false
|
29
|
+
|
30
|
+
option ["-p", "--public-key"],
|
31
|
+
"PUBLIC_KEY", "add ssh public key\n"
|
32
|
+
|
33
|
+
option ["-g", "--group"],
|
34
|
+
"GROUP",
|
35
|
+
"associate the given groups to new user\n",
|
36
|
+
multivalued: true
|
37
|
+
|
38
|
+
parameter "USERNAME",
|
39
|
+
"The username of the user to add\nShould be an email address" do |u|
|
40
|
+
raise ArgumentError.new("Not an email") if u !~ URI::MailTo::EMAIL_REGEXP
|
41
|
+
u
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute
|
45
|
+
|
46
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
47
|
+
ClimateControl.modify(auth_env) do
|
48
|
+
manager = Atmos.config.provider.user_manager
|
49
|
+
user = manager.create_user(username)
|
50
|
+
user.merge!(manager.set_groups(username, group_list, force: force?)) if group_list.present?
|
51
|
+
user.merge!(manager.enable_login(username, force: force?)) if login?
|
52
|
+
user.merge!(manager.enable_mfa(username, force: force?)) if mfa?
|
53
|
+
user.merge!(manager.enable_access_keys(username, force: force?)) if key?
|
54
|
+
user.merge!(manager.set_public_key(username, public_key, force: force?)) if public_key.present?
|
55
|
+
|
56
|
+
logger.info "\nUser created:\n#{display user}\n"
|
57
|
+
|
58
|
+
if mfa? && user[:mfa_secret]
|
59
|
+
save_mfa = agree("Save the MFA secret for runtime integration with auth? ") {|q|
|
60
|
+
q.default = 'y'
|
61
|
+
}
|
62
|
+
Atmos::Otp.instance.save if save_mfa
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/atmos/config.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
require_relative '../atmos/settings_hash'
|
3
|
+
require_relative '../atmos/provider_factory'
|
4
|
+
require 'yaml'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'find'
|
7
|
+
|
8
|
+
module Atmos
|
9
|
+
class Config
|
10
|
+
include GemLogger::LoggerSupport
|
11
|
+
include FileUtils
|
12
|
+
|
13
|
+
attr_accessor :atmos_env, :root_dir,
|
14
|
+
:config_file, :configs_dir,
|
15
|
+
:tmp_root
|
16
|
+
|
17
|
+
def initialize(atmos_env)
|
18
|
+
@atmos_env = atmos_env
|
19
|
+
@root_dir = File.expand_path(Dir.pwd)
|
20
|
+
@config_file = File.join(root_dir, "config", "atmos.yml")
|
21
|
+
@configs_dir = File.join(root_dir, "config", "atmos")
|
22
|
+
@tmp_root = File.join(root_dir, "tmp")
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_atmos_repo?
|
26
|
+
File.exist?(config_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
load
|
31
|
+
result = @config.notation_get(key)
|
32
|
+
return result
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_h
|
36
|
+
load
|
37
|
+
@config.to_hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def provider
|
41
|
+
@provider ||= Atmos::ProviderFactory.get(self[:provider])
|
42
|
+
end
|
43
|
+
|
44
|
+
def all_env_names
|
45
|
+
load
|
46
|
+
@full_config[:environments].keys
|
47
|
+
end
|
48
|
+
|
49
|
+
def account_hash
|
50
|
+
load
|
51
|
+
environments = @full_config[:environments] || {}
|
52
|
+
environments.inject(Hash.new) do |accum, entry|
|
53
|
+
accum[entry.first] = entry.last[:account_id]
|
54
|
+
accum
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def tmp_dir
|
59
|
+
@tmp_dir ||= begin
|
60
|
+
dir = File.join(tmp_root, atmos_env)
|
61
|
+
logger.debug("Tmp dir: #{dir}")
|
62
|
+
mkdir_p(dir)
|
63
|
+
dir
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def auth_cache_dir
|
68
|
+
@auth_cache_dir ||= begin
|
69
|
+
dir = File.join(tmp_dir, 'auth')
|
70
|
+
logger.debug("Auth cache dir: #{dir}")
|
71
|
+
mkdir_p(dir)
|
72
|
+
dir
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def tf_working_dir(group='default')
|
77
|
+
@tf_working_dir ||= {}
|
78
|
+
@tf_working_dir[group] ||= begin
|
79
|
+
dir = File.join(tmp_dir, 'tf', group)
|
80
|
+
logger.debug("Terraform working dir: #{dir}")
|
81
|
+
mkdir_p(dir)
|
82
|
+
dir
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
INTERP_PATTERN = /(\#\{([^\}]+)\})/
|
89
|
+
|
90
|
+
def load
|
91
|
+
@config ||= begin
|
92
|
+
|
93
|
+
logger.debug("Atmos env: #{atmos_env}")
|
94
|
+
|
95
|
+
if ! File.exist?(config_file)
|
96
|
+
logger.warn "Could not find an atmos config file at: #{config_file}"
|
97
|
+
# raise RuntimeError.new("Could not find an atmos config file at: #{config_file}")
|
98
|
+
end
|
99
|
+
|
100
|
+
logger.debug("Loading atmos config file #{config_file}")
|
101
|
+
@full_config = SettingsHash.new((YAML.load_file(config_file) rescue Hash.new))
|
102
|
+
|
103
|
+
if Dir.exist?(configs_dir)
|
104
|
+
logger.debug("Loading atmos config files from #{configs_dir}")
|
105
|
+
Find.find(configs_dir) do |f|
|
106
|
+
if f =~ /\.ya?ml/i
|
107
|
+
logger.debug("Loading atmos config file: #{f}")
|
108
|
+
h = SettingsHash.new(YAML.load_file(f))
|
109
|
+
@full_config = @full_config.merge(h)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
logger.debug("Atmos config dir doesn't exist: #{configs_dir}")
|
114
|
+
end
|
115
|
+
|
116
|
+
@full_config['provider'] = provider_name = @full_config['provider'] || 'aws'
|
117
|
+
global = SettingsHash.new(@full_config.reject {|k, v| ['environments', 'providers'].include?(k) })
|
118
|
+
begin
|
119
|
+
prov = @full_config.deep_fetch(:providers, provider_name)
|
120
|
+
rescue
|
121
|
+
logger.debug("No provider config found for '#{provider_name}'")
|
122
|
+
prov = {}
|
123
|
+
end
|
124
|
+
|
125
|
+
begin
|
126
|
+
env = @full_config.deep_fetch(:environments, atmos_env)
|
127
|
+
rescue
|
128
|
+
logger.debug("No environment config found for '#{atmos_env}'")
|
129
|
+
env = {}
|
130
|
+
end
|
131
|
+
|
132
|
+
conf = global.deep_merge(prov).
|
133
|
+
deep_merge(env).
|
134
|
+
deep_merge(
|
135
|
+
atmos_env: atmos_env,
|
136
|
+
atmos_version: Atmos::VERSION
|
137
|
+
)
|
138
|
+
expand(conf, conf)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def expand(config, obj)
|
143
|
+
case obj
|
144
|
+
when Hash
|
145
|
+
SettingsHash.new(Hash[obj.collect {|k, v| [k, expand(config, v)] }])
|
146
|
+
when Array
|
147
|
+
obj.collect {|i| expand(config, i) }
|
148
|
+
when String
|
149
|
+
result = obj
|
150
|
+
result.scan(INTERP_PATTERN).each do |substr, statement|
|
151
|
+
# TODO: check for cycles
|
152
|
+
if statement =~ /^[\w\.\[\]]$/
|
153
|
+
val = config.notation_get(statement)
|
154
|
+
else
|
155
|
+
# TODO: be consistent with dot notation between eval and
|
156
|
+
# notation_get. eval ends up calling Hashie method_missing,
|
157
|
+
# which returns nil if a key doesn't exist, causing a nil
|
158
|
+
# exception for next item in chain, while notation_get returns
|
159
|
+
# nil gracefully for the entire chain (preferred)
|
160
|
+
begin
|
161
|
+
val = eval(statement, config.instance_eval("binding"))
|
162
|
+
rescue => e
|
163
|
+
file, line = find_config_error(substr)
|
164
|
+
file_msg = file.nil? ? "" : " in #{File.basename(file)}:#{line}"
|
165
|
+
raise RuntimeError.new("Failing config statement '#{substr}'#{file_msg} => #{e.class} #{e.message}")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
result = result.sub(substr, expand(config, val).to_s)
|
169
|
+
end
|
170
|
+
result = true if result == 'true'
|
171
|
+
result = false if result == 'false'
|
172
|
+
result
|
173
|
+
else
|
174
|
+
obj
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def find_config_error(statement)
|
179
|
+
filename = nil
|
180
|
+
line = 0
|
181
|
+
|
182
|
+
configs = []
|
183
|
+
configs << config_file if File.exist?(config_file)
|
184
|
+
if Dir.exist?(configs_dir)
|
185
|
+
Find.find(configs_dir) do |f|
|
186
|
+
if f =~ /\.ya?ml/i
|
187
|
+
configs << f
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
configs.each do |c|
|
193
|
+
current_line = 0
|
194
|
+
File.foreach(c) do |f|
|
195
|
+
current_line += 1
|
196
|
+
if f.include?(statement)
|
197
|
+
filename = c
|
198
|
+
line = current_line
|
199
|
+
break
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
return filename, line
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require_relative '../atmos'
|
2
|
+
require_relative '../atmos/ui'
|
3
|
+
require 'thor'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
module Atmos
|
7
|
+
|
8
|
+
# From https://github.com/rubber/rubber/blob/master/lib/rubber/commands/vulcanize.rb
|
9
|
+
class Generator < Thor
|
10
|
+
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
def initialize(*args, **opts)
|
14
|
+
super
|
15
|
+
@dependencies = opts[:dependencies]
|
16
|
+
end
|
17
|
+
|
18
|
+
no_commands do
|
19
|
+
|
20
|
+
include GemLogger::LoggerSupport
|
21
|
+
include Atmos::UI
|
22
|
+
|
23
|
+
TEMPLATES_SPEC_FILE = 'templates.yml'
|
24
|
+
TEMPLATES_ACTIONS_FILE = 'templates.rb'
|
25
|
+
|
26
|
+
def self.valid_templates
|
27
|
+
all_entries = []
|
28
|
+
source_paths_for_search.collect do |path|
|
29
|
+
entries = []
|
30
|
+
if Dir.exist?(path)
|
31
|
+
Find.find(path) do |f|
|
32
|
+
Find.prune if File.basename(f) =~ /(^\.)|svn|CVS/
|
33
|
+
|
34
|
+
template_spec = File.join(f, TEMPLATES_SPEC_FILE)
|
35
|
+
if File.exist?(template_spec)
|
36
|
+
entries << f.sub(/^#{path}\//, '')
|
37
|
+
Find.prune
|
38
|
+
end
|
39
|
+
end
|
40
|
+
all_entries << entries.sort
|
41
|
+
else
|
42
|
+
logger.warn("Sourcepath does not exist: #{path}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
return all_entries.flatten
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid_templates
|
50
|
+
self.class.valid_templates
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate(template_names)
|
54
|
+
seen = Set.new
|
55
|
+
Array(template_names).each do |template_name|
|
56
|
+
template_dependencies = find_dependencies(template_name)
|
57
|
+
template_dependencies << template_name
|
58
|
+
template_dependencies.each do |tname|
|
59
|
+
apply_template(tname) unless seen.include?(tname)
|
60
|
+
seen << tname
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def template_dir(name)
|
70
|
+
template_dir = nil
|
71
|
+
source_path = nil
|
72
|
+
source_paths.each do |sp|
|
73
|
+
potential_template_dir = File.join(sp, name, '')
|
74
|
+
template_spec = File.join(sp, name, TEMPLATES_SPEC_FILE)
|
75
|
+
if File.exist?(template_spec) && File.directory?(potential_template_dir)
|
76
|
+
template_dir = potential_template_dir
|
77
|
+
source_path = sp
|
78
|
+
break
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
unless template_dir.present?
|
83
|
+
raise ArgumentError.new("Invalid template #{name}, use one of: #{valid_templates.join(', ')}")
|
84
|
+
end
|
85
|
+
|
86
|
+
return template_dir, source_path
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_dependencies(name, seen=[])
|
90
|
+
template_dir, source_path = template_dir(name)
|
91
|
+
|
92
|
+
return [] unless @dependencies
|
93
|
+
|
94
|
+
if seen.include?(name)
|
95
|
+
seen << name
|
96
|
+
raise ArgumentError.new("Circular template dependency: #{seen.to_a.join(" => ")}")
|
97
|
+
end
|
98
|
+
seen << name
|
99
|
+
|
100
|
+
template_conf = load_template_config(template_dir)
|
101
|
+
template_dependencies = Set.new(Array(template_conf['dependent_templates'] || []))
|
102
|
+
|
103
|
+
template_dependencies.clone.each do |dep|
|
104
|
+
template_dependencies.merge(find_dependencies(dep, seen.dup))
|
105
|
+
end
|
106
|
+
|
107
|
+
return template_dependencies.to_a
|
108
|
+
end
|
109
|
+
|
110
|
+
def apply_template(name)
|
111
|
+
template_dir, source_path = template_dir(name)
|
112
|
+
logger.debug("Applying template '#{name}' from '#{template_dir}' in sourcepath '#{source_path}'")
|
113
|
+
template_conf = load_template_config(template_dir)
|
114
|
+
|
115
|
+
extra_generator_steps_file = File.join(template_dir, TEMPLATES_ACTIONS_FILE)
|
116
|
+
|
117
|
+
Find.find(template_dir) do |f|
|
118
|
+
Find.prune if f == File.join(template_dir, TEMPLATES_SPEC_FILE) # don't copy over templates.yml
|
119
|
+
Find.prune if f == extra_generator_steps_file # don't copy over templates.rb
|
120
|
+
|
121
|
+
# Using File.join(x, '') to ensure trailing slash to make sure we end
|
122
|
+
# up with a relative path
|
123
|
+
template_rel = f.gsub(/#{File.join(template_dir, '')}/, '')
|
124
|
+
source_rel = f.gsub(/#{File.join(source_path, '')}/, '')
|
125
|
+
dest_rel = source_rel.gsub(/^#{File.join(name, '')}/, '')
|
126
|
+
|
127
|
+
# prune non-directories at top level (the top level directory is the
|
128
|
+
# template dir itself)
|
129
|
+
if f !~ /\// && ! File.directory?(f)
|
130
|
+
Find.prune
|
131
|
+
end
|
132
|
+
|
133
|
+
# Only include optional files when their conditions eval to true
|
134
|
+
optional = template_conf['optional'][template_rel] rescue nil
|
135
|
+
if optional
|
136
|
+
exclude = ! eval(optional)
|
137
|
+
logger.debug("Optional template '#{template_rel}' with condition: '#{optional}', excluding=#{exclude}")
|
138
|
+
Find.prune if exclude
|
139
|
+
end
|
140
|
+
|
141
|
+
logger.debug("Template '#{source_rel}' => '#{dest_rel}'")
|
142
|
+
if File.directory?(f)
|
143
|
+
empty_directory(dest_rel)
|
144
|
+
else
|
145
|
+
copy_file(source_rel, dest_rel, mode: :preserve)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if File.exist? extra_generator_steps_file
|
150
|
+
eval File.read(extra_generator_steps_file), binding, extra_generator_steps_file
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def load_template_config(template_dir)
|
155
|
+
YAML.load(File.read(File.join(template_dir, 'templates.yml'))) || {} rescue {}
|
156
|
+
end
|
157
|
+
|
158
|
+
def raw_config(yml_file)
|
159
|
+
@raw_configs ||= {}
|
160
|
+
@raw_configs[yml_file] ||= SettingsHash.new((YAML.load_file(yml_file) rescue {}))
|
161
|
+
end
|
162
|
+
|
163
|
+
def add_config(yml_file, key, value, additive: true)
|
164
|
+
new_yml = SettingsHash.add_config(yml_file, key, value, additive: additive)
|
165
|
+
create_file yml_file, new_yml
|
166
|
+
@raw_configs.delete(yml_file) if @raw_configs
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_config(yml_file, key)
|
170
|
+
config = raw_config(yml_file)
|
171
|
+
config.notation_get(key)
|
172
|
+
end
|
173
|
+
|
174
|
+
def config_present?(yml_file, key, value=nil)
|
175
|
+
val = get_config(yml_file, key)
|
176
|
+
|
177
|
+
result = val.present?
|
178
|
+
if value && result
|
179
|
+
if val.is_a?(Array)
|
180
|
+
result = Array(value).all? {|v| val.include?(v) }
|
181
|
+
else
|
182
|
+
result = (val == value)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
return result
|
187
|
+
end
|
188
|
+
|
189
|
+
# TODO make a context object for these actions, and populate it with things
|
190
|
+
# like template_dir from within apply
|
191
|
+
def new_keys?(src_yml_file, dest_yml_file)
|
192
|
+
src = raw_config(src_yml_file).keys.sort
|
193
|
+
dest = raw_config(dest_yml_file).keys.sort
|
194
|
+
(src - dest).size > 0
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|