simplygenius-atmos 0.7.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 +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
|