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.
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