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,136 @@
1
+ require_relative '../atmos'
2
+ require 'fileutils'
3
+ require 'hashie'
4
+
5
+ module SimplyGenius
6
+ module Atmos
7
+
8
+ class Ipc
9
+ include GemLogger::LoggerSupport
10
+
11
+ def initialize(sock_dir=Dir.tmpdir)
12
+ @sock_dir = sock_dir
13
+ end
14
+
15
+ def listen(&block)
16
+ raise "Already listening" if @server
17
+
18
+ begin
19
+ @socket_path = File.join(@sock_dir, 'atmos-ipc')
20
+ FileUtils.rm_f(@socket_path)
21
+ @server = UNIXServer.open(@socket_path)
22
+ rescue ArgumentError => e
23
+ if e.message =~ /too long unix socket path/ && @sock_dir != Dir.tmpdir
24
+ logger.warn("Using tmp for ipc socket as path too long: #{@socket_path}")
25
+ @sock_dir = Dir.tmpdir
26
+ retry
27
+ end
28
+ end
29
+
30
+ begin
31
+ thread = Thread.new { run }
32
+ block.call(@socket_path)
33
+ ensure
34
+ @server.close
35
+ FileUtils.rm_f(@socket_path)
36
+ @server = nil
37
+ end
38
+ end
39
+
40
+ def generate_client_script
41
+ script_file = File.join(@sock_dir, 'atmos_ipc.rb')
42
+ File.write(script_file, <<~EOF
43
+ #!/usr/bin/env ruby
44
+ require 'socket'
45
+ UNIXSocket.open('#{@socket_path}') {|c| c.puts(ARGV[0] || $stdin.read); puts c.gets }
46
+ EOF
47
+ )
48
+ FileUtils.chmod('+x', script_file)
49
+ return script_file
50
+ end
51
+
52
+ private
53
+
54
+ def run
55
+ logger.debug("Starting ipc thread")
56
+ begin
57
+ while @server && sock = @server.accept
58
+ logger.debug("An ipc client connected")
59
+ line = sock.gets
60
+ logger.debug("Got ipc message: #{line.inspect}")
61
+ response = {}
62
+
63
+ begin
64
+ msg = JSON.parse(line)
65
+ msg = Hashie.symbolize_keys(msg)
66
+
67
+ # enabled by default if enabled is not set (e.g. from provisioner local-exec)
68
+ enabled = msg[:enabled].nil? ? true : ["true", "1"].include?(msg[:enabled].to_s)
69
+
70
+ if enabled
71
+ logger.debug("Dispatching IPC action")
72
+ response = dispatch(msg)
73
+ else
74
+ response[:message] = "IPC action is not enabled"
75
+ logger.debug(response[:error])
76
+ end
77
+ rescue => e
78
+ logger.log_exception(e, "Failed to parse ipc message")
79
+ response[:error] = "Failed to parse ipc message #{e.message}"
80
+ end
81
+
82
+ respond(sock, response)
83
+ sock.close
84
+ end
85
+ rescue IOError, EOFError, Errno::EBADF
86
+ nil
87
+ rescue Exception => e
88
+ logger.log_exception(e, "Ipc failure")
89
+ end
90
+ end
91
+
92
+ def close
93
+ @server.close if @server rescue nil
94
+ end
95
+
96
+ def load_action(name)
97
+ action = nil
98
+ logger.debug("Loading ipc action: #{name}")
99
+ begin
100
+ require "simplygenius/atmos/ipc_actions/#{name}"
101
+ action = "SimplyGenius::Atmos::IpcActions::#{name.camelize}".constantize
102
+ logger.debug("Loaded ipc action #{name}")
103
+ rescue LoadError, NameError => e
104
+ logger.log_exception(e, "Failed to load ipc action")
105
+ end
106
+ return action
107
+ end
108
+
109
+ def dispatch(msg)
110
+ response = {}
111
+ action = load_action(msg[:action])
112
+ if action.nil?
113
+ response[:error] = "Unsupported ipc action: #{msg.to_hash.inspect}"
114
+ logger.warn(response[:error])
115
+ else
116
+ begin
117
+ response = action.new().execute(**msg)
118
+ rescue => e
119
+ response[:error] = "Failure while executing ipc action: #{e.message}"
120
+ logger.log_exception(e, "Failure while executing ipc action")
121
+ end
122
+ end
123
+ return response
124
+ end
125
+
126
+ def respond(sock, response)
127
+ msg = JSON.generate(response)
128
+ logger.debug("Sending ipc response: #{msg.inspect}")
129
+ sock.puts(msg)
130
+ sock.flush
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../../atmos'
2
+ require_relative '../../atmos/ui'
3
+
4
+ module SimplyGenius
5
+ module Atmos
6
+ module IpcActions
7
+
8
+ class Notify
9
+ include GemLogger::LoggerSupport
10
+ include UI
11
+
12
+ def initialize()
13
+ end
14
+
15
+ def execute(**opts)
16
+
17
+ result = {
18
+ 'stdout' => '',
19
+ 'success' => ''
20
+ }
21
+
22
+ return result if Atmos.config["ipc.notify.disable"].to_s == "true"
23
+ return notify(**opts)
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../../atmos'
2
+ require 'open3'
3
+ require 'os'
4
+
5
+ module SimplyGenius
6
+ module Atmos
7
+ module IpcActions
8
+
9
+ class Ping
10
+ include GemLogger::LoggerSupport
11
+
12
+ def initialize()
13
+ end
14
+
15
+ def execute(**opts)
16
+ return opts.merge(action: 'pong')
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,164 @@
1
+ require 'logging'
2
+ require 'gem_logger'
3
+ require 'rainbow'
4
+ require 'delegate'
5
+
6
+ module SimplyGenius
7
+ module Atmos
8
+
9
+ module Logging
10
+
11
+ module GemLoggerConcern
12
+ extend ActiveSupport::Concern
13
+
14
+ def logger
15
+ ::Logging.logger[self.class]
16
+ end
17
+
18
+ module ClassMethods
19
+ def logger
20
+ ::Logging.logger[self]
21
+ end
22
+ end
23
+ end
24
+
25
+ class CaptureStream < SimpleDelegator
26
+
27
+ def initialize(logger_name, appender, stream, color=nil)
28
+ super(stream)
29
+ @color = stream.tty? && color ? color : nil
30
+ @logger = ::Logging.logger[logger_name]
31
+ @logger.appenders = [appender]
32
+ @logger.additive = false
33
+ end
34
+
35
+ def strip_color(str)
36
+ str.gsub(/\e\[\d+m/, '')
37
+ end
38
+
39
+ def write(data)
40
+ @logger.info(strip_color(data))
41
+ if @color
42
+ count = 0
43
+ d = data.lines.each do |l|
44
+ cl = Kernel.send(:Rainbow, l).send(@color)
45
+ count += super(cl)
46
+ end
47
+ return count
48
+ else
49
+ return super(data)
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.init_logger
55
+ return if @initialized
56
+ @initialized = true
57
+
58
+ ::Logging.format_as :inspect
59
+ ::Logging.backtrace true
60
+
61
+ ::Logging.color_scheme(
62
+ 'bright',
63
+ lines: {
64
+ debug: :green,
65
+ info: :default,
66
+ warn: :yellow,
67
+ error: :red,
68
+ fatal: [:white, :on_red]
69
+ },
70
+ date: :blue,
71
+ logger: :cyan,
72
+ message: :magenta
73
+ )
74
+
75
+ ::Logging.logger.root.level = :info
76
+ GemLogger.configure do |config|
77
+ config.default_logger = ::Logging.logger.root
78
+ config.logger_concern = Logging::GemLoggerConcern
79
+ end
80
+ end
81
+
82
+
83
+ def self.testing
84
+ @t
85
+ end
86
+
87
+ def self.testing=(t)
88
+ @t = t
89
+ end
90
+
91
+ def self.sio
92
+ ::Logging.logger.root.appenders.find {|a| a.name == 'sio' }
93
+ end
94
+
95
+ def self.contents
96
+ sio.try(:sio).try(:to_s)
97
+ end
98
+
99
+ def self.clear
100
+ sio.try(:clear)
101
+ end
102
+
103
+ def self.setup_logging(debug, color, logfile)
104
+ init_logger
105
+
106
+ ::Logging.logger.root.level = debug ? :debug : :info
107
+ appenders = []
108
+ detail_pattern = '[%d] %-5l %c{2} %m\n'
109
+ plain_pattern = '%m\n'
110
+
111
+ pattern_options = {
112
+ pattern: plain_pattern
113
+ }
114
+ if color
115
+ pattern_options[:color_scheme] = 'bright'
116
+ end
117
+
118
+ if self.testing
119
+
120
+ appender = ::Logging.appenders.string_io(
121
+ 'sio',
122
+ layout: ::Logging.layouts.pattern(pattern_options)
123
+ )
124
+ appenders << appender
125
+
126
+ else
127
+
128
+ appender = ::Logging.appenders.stdout(
129
+ 'stdout',
130
+ layout: ::Logging.layouts.pattern(pattern_options)
131
+ )
132
+ appenders << appender
133
+
134
+ end
135
+
136
+ # Do this after setting up stdout appender so we don't duplicate output
137
+ # to stdout with our capture
138
+ if logfile.present?
139
+
140
+ appender = ::Logging.appenders.file(
141
+ logfile,
142
+ truncate: true,
143
+ layout: ::Logging.layouts.pattern(pattern: detail_pattern)
144
+ )
145
+ appenders << appender
146
+
147
+ if ! $stdout.is_a? CaptureStream
148
+ $stdout = CaptureStream.new("stdout", appender, $stdout)
149
+ $stderr = CaptureStream.new("stderr", appender, $stderr, :red)
150
+ silence_warnings {
151
+ Object.const_set(:STDOUT, $stdout)
152
+ Object.const_set(:STDERR, $stderr)
153
+ }
154
+ end
155
+
156
+ end
157
+
158
+ ::Logging.logger.root.appenders = appenders
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../atmos'
2
+ require 'singleton'
3
+ require 'rotp'
4
+
5
+ module SimplyGenius
6
+ module Atmos
7
+
8
+ class Otp
9
+ include Singleton
10
+ include GemLogger::LoggerSupport
11
+
12
+ def initialize
13
+ @secret_file = Atmos.config["otp.secret_file"] || "~/.atmos.yml"
14
+ @secret_file = File.expand_path(@secret_file)
15
+ yml_hash = YAML.load_file(@secret_file) rescue Hash.new
16
+ @secret_store = SettingsHash.new(yml_hash)
17
+ @secret_store[Atmos.config[:org]] ||= {}
18
+ @secret_store[Atmos.config[:org]][:otp] ||= {}
19
+ @scoped_secret_store = @secret_store[Atmos.config[:org]][:otp]
20
+ end
21
+
22
+ def add(name, secret)
23
+ old = @scoped_secret_store[name]
24
+ logger.info "Replacing OTP secret #{name}=#{old}" if old
25
+ @scoped_secret_store[name] = secret
26
+ end
27
+
28
+ def remove(name)
29
+ old = @scoped_secret_store.delete(name)
30
+ @otp.try(:delete, name)
31
+ logger.info "Removed OTP secret #{name}=#{old}" if old
32
+ end
33
+
34
+ def save
35
+ File.write(@secret_file, YAML.dump(@secret_store.to_hash))
36
+ File.chmod(0600, @secret_file)
37
+ end
38
+
39
+ def generate(name)
40
+ otp(name).try(:now)
41
+ end
42
+
43
+ private
44
+
45
+ def otp(name)
46
+ @otp ||= {}
47
+ @otp[name] ||= begin
48
+ secret = @scoped_secret_store[name]
49
+ totp = nil
50
+ if secret
51
+ totp = ROTP::TOTP.new(secret)
52
+ else
53
+ logger.debug "OTP secret does not exist for '#{name}'"
54
+ end
55
+ totp
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ require_relative '../atmos'
2
+ require 'active_support/core_ext/class'
3
+
4
+ Dir.glob(File.join(File.join(__dir__, 'plugins'), '*.rb')) do |f|
5
+ require_relative "plugins/#{File.basename(f).sub(/\.rb$/, "")}"
6
+ end
7
+
8
+ module SimplyGenius
9
+ module Atmos
10
+
11
+ class Plugin
12
+ include GemLogger::LoggerSupport
13
+
14
+ attr_reader :config
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ def register_output_filter(type, clazz)
21
+ Atmos.config.plugin_manager.register_output_filter(type, clazz)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end