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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/exe/atmos +2 -2
  4. data/lib/{atmos.rb → simplygenius/atmos.rb} +9 -7
  5. data/lib/simplygenius/atmos/cli.rb +116 -0
  6. data/lib/simplygenius/atmos/commands/account.rb +69 -0
  7. data/lib/simplygenius/atmos/commands/apply.rb +24 -0
  8. data/lib/simplygenius/atmos/commands/auth_exec.rb +34 -0
  9. data/lib/simplygenius/atmos/commands/base_command.rb +16 -0
  10. data/lib/simplygenius/atmos/commands/bootstrap.rb +76 -0
  11. data/lib/simplygenius/atmos/commands/container.rb +62 -0
  12. data/lib/simplygenius/atmos/commands/destroy.rb +22 -0
  13. data/lib/simplygenius/atmos/commands/generate.rb +187 -0
  14. data/lib/simplygenius/atmos/commands/init.rb +22 -0
  15. data/lib/simplygenius/atmos/commands/new.rb +22 -0
  16. data/lib/simplygenius/atmos/commands/otp.rb +58 -0
  17. data/lib/simplygenius/atmos/commands/plan.rb +24 -0
  18. data/lib/simplygenius/atmos/commands/secret.rb +91 -0
  19. data/lib/simplygenius/atmos/commands/terraform.rb +56 -0
  20. data/lib/simplygenius/atmos/commands/user.rb +78 -0
  21. data/lib/simplygenius/atmos/config.rb +279 -0
  22. data/lib/simplygenius/atmos/exceptions.rb +13 -0
  23. data/lib/simplygenius/atmos/generator.rb +232 -0
  24. data/lib/simplygenius/atmos/ipc.rb +136 -0
  25. data/lib/simplygenius/atmos/ipc_actions/notify.rb +31 -0
  26. data/lib/simplygenius/atmos/ipc_actions/ping.rb +23 -0
  27. data/lib/simplygenius/atmos/logging.rb +164 -0
  28. data/lib/simplygenius/atmos/otp.rb +62 -0
  29. data/lib/simplygenius/atmos/plugin.rb +27 -0
  30. data/lib/simplygenius/atmos/plugin_manager.rb +120 -0
  31. data/lib/simplygenius/atmos/plugins/output_filter.rb +29 -0
  32. data/lib/simplygenius/atmos/plugins/prompt_notify.rb +21 -0
  33. data/lib/simplygenius/atmos/provider_factory.rb +23 -0
  34. data/lib/simplygenius/atmos/providers/aws/account_manager.rb +83 -0
  35. data/lib/simplygenius/atmos/providers/aws/auth_manager.rb +220 -0
  36. data/lib/simplygenius/atmos/providers/aws/container_manager.rb +118 -0
  37. data/lib/simplygenius/atmos/providers/aws/provider.rb +53 -0
  38. data/lib/simplygenius/atmos/providers/aws/s3_secret_manager.rb +51 -0
  39. data/lib/simplygenius/atmos/providers/aws/user_manager.rb +213 -0
  40. data/lib/simplygenius/atmos/settings_hash.rb +93 -0
  41. data/lib/simplygenius/atmos/source_path.rb +186 -0
  42. data/lib/simplygenius/atmos/template.rb +117 -0
  43. data/lib/simplygenius/atmos/terraform_executor.rb +297 -0
  44. data/lib/simplygenius/atmos/ui.rb +173 -0
  45. data/lib/simplygenius/atmos/utils.rb +54 -0
  46. data/lib/simplygenius/atmos/version.rb +5 -0
  47. data/templates/new/config/atmos.yml +21 -13
  48. data/templates/new/config/atmos/recipes.yml +16 -0
  49. data/templates/new/config/atmos/runtime.yml +9 -0
  50. metadata +46 -40
  51. data/lib/atmos/cli.rb +0 -105
  52. data/lib/atmos/commands/account.rb +0 -65
  53. data/lib/atmos/commands/apply.rb +0 -20
  54. data/lib/atmos/commands/auth_exec.rb +0 -29
  55. data/lib/atmos/commands/base_command.rb +0 -12
  56. data/lib/atmos/commands/bootstrap.rb +0 -72
  57. data/lib/atmos/commands/container.rb +0 -58
  58. data/lib/atmos/commands/destroy.rb +0 -18
  59. data/lib/atmos/commands/generate.rb +0 -90
  60. data/lib/atmos/commands/init.rb +0 -18
  61. data/lib/atmos/commands/new.rb +0 -18
  62. data/lib/atmos/commands/otp.rb +0 -54
  63. data/lib/atmos/commands/plan.rb +0 -20
  64. data/lib/atmos/commands/secret.rb +0 -87
  65. data/lib/atmos/commands/terraform.rb +0 -52
  66. data/lib/atmos/commands/user.rb +0 -74
  67. data/lib/atmos/config.rb +0 -208
  68. data/lib/atmos/exceptions.rb +0 -9
  69. data/lib/atmos/generator.rb +0 -199
  70. data/lib/atmos/generator_factory.rb +0 -93
  71. data/lib/atmos/ipc.rb +0 -132
  72. data/lib/atmos/ipc_actions/notify.rb +0 -27
  73. data/lib/atmos/ipc_actions/ping.rb +0 -19
  74. data/lib/atmos/logging.rb +0 -160
  75. data/lib/atmos/otp.rb +0 -61
  76. data/lib/atmos/provider_factory.rb +0 -19
  77. data/lib/atmos/providers/aws/account_manager.rb +0 -82
  78. data/lib/atmos/providers/aws/auth_manager.rb +0 -208
  79. data/lib/atmos/providers/aws/container_manager.rb +0 -116
  80. data/lib/atmos/providers/aws/provider.rb +0 -51
  81. data/lib/atmos/providers/aws/s3_secret_manager.rb +0 -49
  82. data/lib/atmos/providers/aws/user_manager.rb +0 -211
  83. data/lib/atmos/settings_hash.rb +0 -90
  84. data/lib/atmos/terraform_executor.rb +0 -267
  85. data/lib/atmos/ui.rb +0 -159
  86. data/lib/atmos/utils.rb +0 -50
  87. data/lib/atmos/version.rb +0 -3
@@ -0,0 +1,186 @@
1
+ require_relative '../atmos'
2
+ require_relative '../atmos/ui'
3
+ require_relative '../atmos/template'
4
+ require 'find'
5
+ require 'tmpdir'
6
+ require 'fileutils'
7
+ require 'git'
8
+ require 'open-uri'
9
+ require 'zip'
10
+
11
+ module SimplyGenius
12
+ module Atmos
13
+
14
+ class SourcePath
15
+ include GemLogger::LoggerSupport
16
+
17
+ class_attribute :registry, default: {}
18
+ attr_reader :name, :location
19
+
20
+ def self.clear_registry
21
+ registry.clear
22
+ @resolved_templates.clear if @resolved_templates
23
+ end
24
+
25
+ def self.register(name, location)
26
+ sp = SourcePath.new(name, location)
27
+ raise ArgumentError.new("Source paths must be uniquely named: #{sp}") if registry[name]
28
+ registry[name] = sp
29
+ end
30
+
31
+ def self.find_template(template_name)
32
+ @resolved_templates ||= {}
33
+ @resolved_templates[template_name] ||= begin
34
+ tmpls = registry.collect {|name, sp| sp.template(template_name) }.compact
35
+
36
+ if tmpls.size == 0
37
+ raise ArgumentError.new("Could not find the template: #{template_name}")
38
+ elsif tmpls.size > 1
39
+ raise ArgumentError.new("Template names must be unique, #{template_name} exists in multiple sources: #{tmpls.collect(&:source)}")
40
+ end
41
+
42
+ tmpls.first
43
+ end
44
+ end
45
+
46
+ def initialize(name, location)
47
+ @name = name
48
+ @location = location
49
+ end
50
+
51
+ def to_s
52
+ "#{name} (#{location})"
53
+ end
54
+
55
+ def to_h
56
+ SettingsHash.new({name: name, location: location})
57
+ end
58
+
59
+ def directory
60
+ if @directory_resolved
61
+ @directory
62
+ else
63
+ @directory_resolved = true
64
+ @directory = expand_location
65
+ end
66
+ end
67
+
68
+ def template_names
69
+ templates.keys.sort
70
+ end
71
+
72
+ def template(name)
73
+ templates[name]
74
+ end
75
+
76
+ protected
77
+
78
+ def expand_location
79
+ sourcepath_dir = nil
80
+ sourcepath = location
81
+ if sourcepath =~ /(\.git)|(\.zip)(#.*)?$/
82
+
83
+ logger.debug("Using archive sourcepath")
84
+
85
+ tmpdir = Dir.mktmpdir("atmos-templates-")
86
+ at_exit { FileUtils.remove_entry(tmpdir) }
87
+
88
+ template_subdir = ''
89
+ if sourcepath =~ /([^#]*)#([^#]*)/
90
+ sourcepath = Regexp.last_match[1]
91
+ template_subdir = Regexp.last_match[2]
92
+ logger.debug("Using archive subdirectory for templates: #{template_subdir}")
93
+ end
94
+
95
+ if sourcepath =~ /.git$/
96
+
97
+ begin
98
+ logger.debug("Cloning git archive to tmpdir")
99
+
100
+ g = Git.clone(sourcepath, 'atmos-checkout', depth: 1, path: tmpdir)
101
+ local_template_path = File.join(g.dir.path, template_subdir)
102
+
103
+ sourcepath_dir = File.expand_path(local_template_path)
104
+ logger.debug("Using git sourcepath: #{sourcepath_dir}")
105
+ rescue => e
106
+ msg = "Could not read from git archive, ignoring sourcepath: #{name}, #{location}"
107
+ logger.log_exception(e, msg, level: :debug)
108
+ logger.warn(msg)
109
+ end
110
+
111
+ elsif sourcepath =~ /.zip$/
112
+
113
+ begin
114
+ logger.debug("Cloning zip archive to tmpdir")
115
+
116
+ open(sourcepath, 'rb') do |io|
117
+ Zip::File.open_buffer(io) do |zip_file|
118
+ zip_file.each do |f|
119
+ fpath = File.join(tmpdir, f.name)
120
+ f.extract(fpath)
121
+ end
122
+ end
123
+ end
124
+
125
+ local_template_path = File.join(tmpdir, template_subdir)
126
+ sourcepath_dir = File.expand_path(local_template_path)
127
+ logger.debug("Using zip sourcepath: #{sourcepath_dir}")
128
+ rescue => e
129
+ msg = "Could not read from zip archive, ignoring sourcepath: #{name}, #{location}"
130
+ logger.log_exception(e, msg, level: :debug)
131
+ logger.warn(msg)
132
+ end
133
+
134
+ end
135
+
136
+ else
137
+
138
+ sourcepath_dir = File.expand_path(sourcepath)
139
+ logger.debug("Using local sourcepath: #{sourcepath_dir}")
140
+
141
+ end
142
+
143
+ sourcepath_dir
144
+ end
145
+
146
+ def template_dirs
147
+ @template_dirs ||= begin
148
+ template_dirs = {}
149
+ if directory && Dir.exist?(directory)
150
+
151
+ Find.find(directory) do |f|
152
+ Find.prune if File.basename(f) =~ /(^\.)|svn|CVS|git/
153
+
154
+ template_spec = File.join(f, Template::TEMPLATES_SPEC_FILE)
155
+ if File.exist?(template_spec)
156
+ template_name = f.sub(/^#{directory}\//, '')
157
+
158
+ if template_dirs[template_name]
159
+ # safety, this should never get hit
160
+ raise "A single source path cannot have duplicate templates: #{f}"
161
+ end
162
+ template_dirs[template_name] = f
163
+ Find.prune
164
+ end
165
+ end
166
+
167
+ else
168
+
169
+ logger.warn("Sourcepath directory does not exist for location: #{location}, #{directory}")
170
+
171
+ end
172
+
173
+ template_dirs
174
+ end
175
+ end
176
+
177
+ def templates
178
+ @templates ||= Hash[template_dirs.collect do |tname, dir|
179
+ [tname, Template.new(tname, dir, self)]
180
+ end]
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,117 @@
1
+ require_relative '../atmos'
2
+ require_relative '../atmos/source_path'
3
+
4
+ module SimplyGenius
5
+ module Atmos
6
+
7
+ class Template
8
+ include GemLogger::LoggerSupport
9
+
10
+ TEMPLATES_SPEC_FILE = 'templates.yml'
11
+ TEMPLATES_ACTIONS_FILE = 'templates.rb'
12
+
13
+ attr_reader :name, :directory, :source, :context
14
+
15
+ def initialize(name, directory, source, context: {})
16
+ @name = name
17
+ @directory = directory
18
+ @source = source
19
+ @context = context
20
+ @context = SettingsHash.new(@context) unless @context.kind_of?(SettingsHash)
21
+ end
22
+
23
+ def to_s
24
+ "#{name}"
25
+ end
26
+
27
+ def to_h
28
+ SettingsHash.new({name: name, source: source.to_h, context: context})
29
+ end
30
+
31
+ def context_path
32
+ name.gsub('-', '_').gsub('/', '.')
33
+ end
34
+
35
+ def scoped_context
36
+ result = context.notation_get(context_path)
37
+ if result.nil?
38
+ context.notation_put(context_path, SettingsHash.new, additive: false)
39
+ result = context.notation_get(context_path)
40
+ end
41
+ result
42
+ end
43
+
44
+ def actions_path
45
+ File.join(directory, TEMPLATES_ACTIONS_FILE)
46
+ end
47
+
48
+ def actions
49
+ @actions ||= (File.exist?(actions_path) ? File.read(actions_path) : "")
50
+ end
51
+
52
+ def config_path
53
+ File.join(directory, TEMPLATES_SPEC_FILE)
54
+ end
55
+
56
+ def config
57
+ @config ||= begin
58
+ data = File.read(config_path)
59
+ SettingsHash.new(YAML.load(data) || {})
60
+ end
61
+ end
62
+
63
+ def optional
64
+ result = config[:optional] || {}
65
+ raise TypeError.new("Template config item :optional must be a hash: #{result.inspect}") unless result.is_a?(Hash)
66
+ result
67
+ end
68
+
69
+ def dependencies
70
+ @dependencies ||= begin
71
+ deps = Array(config[:dependent_templates])
72
+ deps.collect do |d|
73
+ if d.kind_of?(String)
74
+ tmpl = SourcePath.find_template(d)
75
+ elsif d.kind_of?(Hash)
76
+ raise ArgumentError.new("Template must be named with name key: #{tmpl}") unless d[:name]
77
+ tmpl = SourcePath.find_template(d[:name])
78
+ tmpl.context.merge!(d[:context]) if d[:context]
79
+ else
80
+ raise TypeError.new("Invalid template structure: #{d}")
81
+ end
82
+
83
+ tmpl
84
+ end
85
+ end
86
+ end
87
+
88
+ def dup
89
+ dependencies
90
+ Marshal.load(Marshal.dump(self))
91
+ end
92
+
93
+ # depth first iteration of dependencies
94
+ def walk_dependencies(seen=Set.new)
95
+ Enumerator.new do |yielder|
96
+ if seen.include?(name)
97
+ seen << name
98
+ raise ArgumentError.new("Circular template dependency: #{seen.to_a.join(" => ")}")
99
+ end
100
+ seen << name
101
+
102
+ dependencies.each do |dep|
103
+
104
+ dep = dep.dup
105
+ dep.context.merge!(context)
106
+ dep.walk_dependencies(seen.dup).each do |d|
107
+ yielder << d
108
+ end
109
+ end
110
+ yielder << dup
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,297 @@
1
+ require_relative '../atmos'
2
+ require_relative '../atmos/ipc'
3
+ require_relative '../atmos/ui'
4
+ require 'open3'
5
+ require 'fileutils'
6
+ require 'find'
7
+ require 'climate_control'
8
+
9
+ module SimplyGenius
10
+ module Atmos
11
+
12
+ class TerraformExecutor
13
+ include GemLogger::LoggerSupport
14
+ include FileUtils
15
+ include UI
16
+
17
+ class ProcessFailed < RuntimeError; end
18
+
19
+ def initialize(process_env: ENV, working_group: 'default')
20
+ @process_env = process_env
21
+ @working_group = working_group
22
+ @working_dir = Atmos.config.tf_working_dir(@working_group)
23
+ @recipes = Atmos.config["recipes.#{@working_group}"]
24
+ end
25
+
26
+ def run(*terraform_args, skip_backend: false, skip_secrets: false, get_modules: false, output_io: nil)
27
+ setup_working_dir(skip_backend: skip_backend)
28
+
29
+ if get_modules
30
+ logger.debug("Getting modules")
31
+ get_modules_io = StringIO.new
32
+ begin
33
+ execute("get", output_io: get_modules_io)
34
+ rescue TerraformExecutor::ProcessFailed => e
35
+ logger.info(get_modules_io.string)
36
+ raise
37
+ end
38
+ end
39
+
40
+ return execute(*terraform_args, skip_secrets: skip_secrets, output_io: output_io)
41
+ end
42
+
43
+ private
44
+
45
+ def tf_cmd(*args)
46
+ ['terraform'] + args
47
+ end
48
+
49
+ def execute(*terraform_args, skip_secrets: false, output_io: nil)
50
+ cmd = tf_cmd(*terraform_args)
51
+ logger.debug("Running terraform: #{cmd.join(' ')}")
52
+
53
+ env = Hash[@process_env]
54
+ if ! skip_secrets
55
+ begin
56
+ env = env.merge(secrets_env)
57
+ rescue => e
58
+ logger.debug("Secrets not available: #{e}")
59
+ end
60
+ end
61
+
62
+ # lets tempfiles created by subprocesses be easily found by users
63
+ env['TMPDIR'] = Atmos.config.tmp_dir
64
+
65
+ # Lets terraform communicate back to atmos, e.g. for UI notifications
66
+ ipc = Ipc.new(Atmos.config.tmp_dir)
67
+
68
+ IO.pipe do |stdout, stdout_writer|
69
+ IO.pipe do |stderr, stderr_writer|
70
+
71
+ stdout_writer.sync = stderr_writer.sync = true
72
+
73
+ stdout_filters = Atmos.config.plugin_manager.output_filters(:stdout, {process_env: @process_env, working_group: @working_group})
74
+ stderr_filters = Atmos.config.plugin_manager.output_filters(:stderr, {process_env: @process_env, working_group: @working_group})
75
+
76
+ stdout_thr = pipe_stream(stdout, output_io.nil? ? $stdout : output_io, &stdout_filters.filter_block)
77
+ stderr_thr = pipe_stream(stderr, output_io.nil? ? $stderr : output_io, &stderr_filters.filter_block)
78
+
79
+ ipc.listen do |sock_path|
80
+
81
+ if Atmos.config['ipc.disable']
82
+ # Using : as the command makes execution of ipc from the
83
+ # terraform side a no-op in both cases of how we call it. This
84
+ # way, terraform execution continues to work when IPC is disabled
85
+ # command = "$ATMOS_IPC_CLIENT <json_string>"
86
+ # program = ["sh", "-c", "$ATMOS_IPC_CLIENT"]
87
+ env['ATMOS_IPC_CLIENT'] = ":"
88
+ else
89
+ env['ATMOS_IPC_SOCK'] = sock_path
90
+ env['ATMOS_IPC_CLIENT'] = ipc.generate_client_script
91
+ end
92
+
93
+ # Was unable to get piping to work with stdin for some reason. It
94
+ # worked in simple case, but started to fail when terraform config
95
+ # got more extensive. Thus, using spawn to redirect stdin from the
96
+ # terminal direct to terraform, with IO.pipe to copy the outher
97
+ # streams. Maybe in the future we can completely disconnect stdin
98
+ # and have atmos do the output parsing and stdin prompting
99
+ pid = spawn(env, *cmd,
100
+ chdir: tf_recipes_dir,
101
+ :out=>stdout_writer, :err=> stderr_writer, :in => :in)
102
+
103
+ logger.debug("Terraform started with pid #{pid}")
104
+ begin
105
+ Process.wait(pid)
106
+ rescue Interrupt
107
+ logger.warn "Got SIGINT, sending to terraform pid=#{pid}"
108
+
109
+ Process.kill("INT", pid)
110
+ Process.wait(pid)
111
+
112
+ logger.debug "Completed signal cleanup"
113
+ exit!(1)
114
+ end
115
+
116
+ end
117
+
118
+ stdout_writer.close
119
+ stderr_writer.close
120
+ stdout_thr.join
121
+ stderr_thr.join
122
+ stdout_filters.close
123
+ stderr_filters.close
124
+
125
+ status = $?.exitstatus
126
+ logger.debug("Terraform exited: #{status}")
127
+ if status != 0
128
+ raise ProcessFailed.new "Terraform exited with non-zero exit code: #{status}"
129
+ end
130
+
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ def setup_working_dir(skip_backend: false)
137
+ clean_links
138
+ link_shared_plugin_dir
139
+ link_support_dirs
140
+ link_recipes
141
+ write_atmos_vars
142
+ setup_backend(skip_backend)
143
+ end
144
+
145
+ def setup_backend(skip_backend=false)
146
+ backend_file = File.join(tf_recipes_dir, 'atmos-backend.tf.json')
147
+ backend_config = (Atmos.config["backend"] || {}).clone
148
+
149
+ if backend_config.present? && ! skip_backend
150
+ logger.debug("Writing out terraform state backend config")
151
+
152
+ # Use a different state file per group
153
+ if @working_group
154
+ backend_config['key'] = "#{@working_group}-#{backend_config['key']}"
155
+ end
156
+
157
+ backend_type = backend_config.delete("type")
158
+
159
+ backend = {
160
+ "terraform" => {
161
+ "backend" => {
162
+ backend_type => backend_config
163
+ }
164
+ }
165
+ }
166
+
167
+ File.write(backend_file, JSON.pretty_generate(backend))
168
+ else
169
+ logger.debug("Clearing terraform state backend config")
170
+ File.delete(backend_file) if File.exist?(backend_file)
171
+ end
172
+ end
173
+
174
+ # terraform currently (v0.11.7) doesn't handle maps with nested maps or
175
+ # lists well, so flatten them - nested maps get expanded into the top level
176
+ # one, with their keys being appended with underscores, and lists get
177
+ # joined with "," so we end up with a single hash with homogenous types
178
+ #
179
+ def homogenize_for_terraform(obj, prefix="")
180
+ if obj.is_a? Hash
181
+ result = {}
182
+ obj.each do |k, v|
183
+ ho = homogenize_for_terraform(v, "#{prefix}#{k}_")
184
+ if ho.is_a? Hash
185
+ result = result.merge(ho)
186
+ else
187
+ result["#{prefix}#{k}"] = ho
188
+ end
189
+ end
190
+ return result
191
+ elsif obj.is_a? Array
192
+ result = []
193
+ obj.each do |o|
194
+ ho = homogenize_for_terraform(o, prefix)
195
+ if ho.is_a? Hash
196
+ result << ho.collect {|k, v| "#{k}=#{v}"}.join(";")
197
+ else
198
+ result << ho
199
+ end
200
+ end
201
+ return result.join(",")
202
+ else
203
+ return obj
204
+ end
205
+ end
206
+
207
+ def tf_recipes_dir
208
+ @tf_recipes_dir ||= begin
209
+ dir = File.join(@working_dir, 'recipes')
210
+ logger.debug("Tf recipes dir: #{dir}")
211
+ mkdir_p(dir)
212
+ dir
213
+ end
214
+ end
215
+
216
+ def write_atmos_vars
217
+ File.open(File.join(tf_recipes_dir, 'atmos.auto.tfvars.json'), 'w') do |f|
218
+ # A mapping in the auto vars file is ignored if a variable declaration doesn't exist for it in a tf file. Thus,
219
+ # as a convenience to allow everything from atmos to be referenceable, we put everything from the atmos_config
220
+ # in a homogenized hash named atmos_config which is declared by the atmos scaffolding. For variables which are
221
+ # declared, we also merge in atmos config with only the values homogenized (vs the entire map) so that hash
222
+ # variables if declared in terraform can be managed from yml, set here and accessed from terraform
223
+ #
224
+ atmos_config = homogenize_for_terraform(Atmos.config.to_h)
225
+ var_hash = {
226
+ atmos_env: Atmos.config.atmos_env,
227
+ all_env_names: Atmos.config.all_env_names,
228
+ account_ids: Atmos.config.account_hash,
229
+ atmos_config: atmos_config
230
+ }
231
+ var_hash = var_hash.merge(Atmos.config.to_h)
232
+ f.puts(JSON.pretty_generate(var_hash))
233
+ end
234
+ end
235
+
236
+ def secrets_env
237
+ # NOTE use an auto-deleting temp file if passing secrets through env ends
238
+ # up being problematic
239
+ # TODO fix the need for CC - TE calls for secrets which needs auth in
240
+ # ENV, so kinda clunk to have to do both CC and pass the env in
241
+ ClimateControl.modify(@process_env) do
242
+ secrets = Atmos.config.provider.secret_manager.to_h
243
+ env_secrets = Hash[secrets.collect { |k, v| ["TF_VAR_#{k}", v] }]
244
+ return env_secrets
245
+ end
246
+ end
247
+
248
+ def clean_links
249
+ Find.find(@working_dir) do |f|
250
+ Find.prune if f =~ /\/.terraform\/modules\//
251
+ File.delete(f) if File.symlink?(f)
252
+ end
253
+ end
254
+
255
+ def link_support_dirs
256
+ ['modules', 'templates'].each do |subdir|
257
+ ln_sf(File.join(Atmos.config.root_dir, subdir), @working_dir)
258
+ end
259
+ end
260
+
261
+ def link_shared_plugin_dir
262
+ if ! Atmos.config["terraform.disable_shared_plugins"]
263
+ shared_plugins_dir = File.join(Atmos.config.tmp_root, "terraform_plugins")
264
+ mkdir_p(shared_plugins_dir)
265
+ terraform_state_dir = File.join(tf_recipes_dir, '.terraform')
266
+ mkdir_p(terraform_state_dir)
267
+ terraform_plugins_dir = File.join(terraform_state_dir, 'plugins')
268
+ ln_sf(shared_plugins_dir, terraform_plugins_dir)
269
+ end
270
+ end
271
+
272
+ def link_recipes
273
+ @recipes.each do |recipe|
274
+ ln_sf(File.join(Atmos.config.root_dir, 'recipes', "#{recipe}.tf"), tf_recipes_dir)
275
+ end
276
+ end
277
+
278
+ def pipe_stream(src, dest)
279
+ Thread.new do
280
+ block_size = 1024
281
+ begin
282
+ while data = src.readpartial(block_size)
283
+ data = yield data if block_given?
284
+ dest.write(data)
285
+ end
286
+ rescue IOError, EOFError => e
287
+ logger.log_exception(e, "Stream failure", level: :debug)
288
+ rescue Exception => e
289
+ logger.log_exception(e, "Stream failure")
290
+ end
291
+ end
292
+ end
293
+
294
+ end
295
+
296
+ end
297
+ end