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
@@ -1,116 +0,0 @@
1
- require_relative '../../../atmos'
2
- require 'aws-sdk-ecs'
3
- require 'aws-sdk-ecr'
4
- require 'open3'
5
-
6
- module Atmos
7
- module Providers
8
- module Aws
9
-
10
- class ContainerManager
11
- include GemLogger::LoggerSupport
12
-
13
- def initialize(provider)
14
- @provider = provider
15
- end
16
-
17
- def push(ecs_name, local_image,
18
- ecr_repo: ecs_name, revision: nil)
19
-
20
- revision = Time.now.strftime('%Y%m%d%H%M%S') unless revision.present?
21
- result = {}
22
-
23
- ecr = ::Aws::ECR::Client.new
24
- resp = nil
25
-
26
- resp = ecr.get_authorization_token
27
- auth_data = resp.authorization_data.first
28
- token = auth_data.authorization_token
29
- endpoint = auth_data.proxy_endpoint
30
- user, password = Base64.decode64(token).split(':')
31
-
32
- # docker login into the ECR repo for the current account so that we can pull/push to it
33
- run("docker", "login", "-u", user, "-p", password, endpoint)#, stdin_data: token)
34
-
35
- image="#{ecs_name}:latest"
36
- ecs_image="#{endpoint.sub(/https?:\/\//, '')}/#{ecr_repo}"
37
-
38
- tags = ['latest', revision]
39
- logger.info "Tagging local image '#{local_image}' with #{tags}"
40
- tags.each {|t| run("docker", "tag", local_image, "#{ecs_image}:#{t}") }
41
-
42
- logger.info "Pushing tagged image to ECR repo"
43
- tags.each {|t| run("docker", "push", "#{ecs_image}:#{t}") }
44
-
45
- result[:remote_image] = "#{ecs_image}:#{revision}"
46
- return result
47
- end
48
-
49
- def deploy_task(task, remote_image)
50
- result = {}
51
-
52
- ecs = ::Aws::ECS::Client.new
53
- resp = nil
54
-
55
- resp = ecs.list_task_definitions(family_prefix: task, sort: 'DESC')
56
- latest_defn_arn = resp.task_definition_arns.first
57
-
58
- logger.info "Latest task definition: #{latest_defn_arn}"
59
-
60
- resp = ecs.describe_task_definition(task_definition: latest_defn_arn)
61
- latest_defn = resp.task_definition
62
-
63
- new_defn = latest_defn.to_h
64
- [:revision, :status, :task_definition_arn,
65
- :requires_attributes, :compatibilities].each do |attr|
66
- new_defn.delete(attr)
67
- end
68
- new_defn[:container_definitions].each {|c| c[:image] = remote_image}
69
-
70
- resp = ecs.register_task_definition(**new_defn)
71
- result[:task_definition] = resp.task_definition.task_definition_arn
72
-
73
- logger.info "Updated task=#{task} to #{result[:task_definition]} with image #{remote_image}"
74
-
75
- return result
76
- end
77
-
78
- def deploy_service(cluster, service, remote_image)
79
- result = {}
80
-
81
- ecs = ::Aws::ECS::Client.new
82
- resp = nil
83
-
84
- # Get current task definition name from service
85
- resp = ecs.describe_services(cluster: cluster, services: [service])
86
- current_defn_arn = resp.services.first.task_definition
87
- defn_name = current_defn_arn.split("/").last.split(":").first
88
-
89
- logger.info "Current task definition (name=#{defn_name}): #{current_defn_arn}"
90
- result = deploy_task(defn_name, remote_image)
91
- new_taskdef = result[:task_definition]
92
-
93
- logger.info "Updating service with new task definition: #{new_taskdef}"
94
-
95
- resp = ecs.update_service(cluster: cluster, service: service, task_definition: new_taskdef)
96
-
97
- logger.info "Updated service=#{service} on cluster=#{cluster} to #{new_taskdef} with image #{remote_image}"
98
-
99
- return result
100
- end
101
-
102
- private
103
-
104
- def run(*args, **opts)
105
- logger.debug("Running: #{args}")
106
- stdout, status = Open3.capture2e(ENV, *args, **opts)
107
- logger.debug(stdout)
108
- raise "Failed to run #{args}: #{stdout}" unless status.success?
109
- return stdout
110
- end
111
-
112
- end
113
-
114
- end
115
- end
116
- end
@@ -1,51 +0,0 @@
1
- require_relative '../../../atmos'
2
-
3
- Dir.glob(File.join(__dir__, '*.rb')) do |f|
4
- require_relative "#{File.basename(f).sub(/\.rb$/, "")}"
5
- end
6
-
7
- module Atmos
8
- module Providers
9
- module Aws
10
-
11
- class Provider
12
- include GemLogger::LoggerSupport
13
-
14
- def initialize(name)
15
- @name = name
16
- end
17
-
18
- def auth_manager
19
- @auth_manager ||= begin
20
- Atmos::Providers::Aws::AuthManager.new(self)
21
- end
22
- end
23
-
24
- def user_manager
25
- @user_manager ||= begin
26
- Atmos::Providers::Aws::UserManager.new(self)
27
- end
28
- end
29
-
30
- def account_manager
31
- @account_manager ||= begin
32
- Atmos::Providers::Aws::AccountManager.new(self)
33
- end
34
- end
35
-
36
- def secret_manager
37
- @secret_manager ||= begin
38
- Atmos::Providers::Aws::S3SecretManager.new(self)
39
- end
40
- end
41
-
42
- def container_manager
43
- @container_manager ||= begin
44
- Atmos::Providers::Aws::ContainerManager.new(self)
45
- end
46
- end
47
- end
48
-
49
- end
50
- end
51
- end
@@ -1,49 +0,0 @@
1
- require_relative '../../../atmos'
2
- require 'aws-sdk-s3'
3
-
4
- module Atmos
5
- module Providers
6
- module Aws
7
-
8
- class S3SecretManager
9
- include GemLogger::LoggerSupport
10
-
11
- def initialize(provider)
12
- @provider = provider
13
- logger.debug("Secrets config is: #{Atmos.config[:secret]}")
14
- @bucket_name = Atmos.config[:secret][:bucket]
15
- @encrypt = Atmos.config[:secret][:encrypt]
16
- end
17
-
18
- def set(key, value)
19
- opts = {}
20
- opts[:server_side_encryption] = "AES256" if @encrypt
21
- bucket.object(key).put(body: value, **opts)
22
- end
23
-
24
- def get(key)
25
- bucket.object(key).get.body.read
26
- end
27
-
28
- def delete(key)
29
- bucket.object(key).delete
30
- end
31
-
32
- def to_h
33
- Hash[bucket.objects.collect {|o|
34
- [o.key, o.get.body.read]
35
- }]
36
- end
37
-
38
- private
39
-
40
- def bucket
41
- raise ArgumentError.new("The s3 secret bucket is not set") unless @bucket_name
42
- @bucket ||= ::Aws::S3::Bucket.new(@bucket_name)
43
- end
44
-
45
- end
46
-
47
- end
48
- end
49
- end
@@ -1,211 +0,0 @@
1
- require_relative '../../../atmos'
2
- require_relative '../../../atmos/otp'
3
- require 'aws-sdk-iam'
4
- require 'securerandom'
5
-
6
- module Atmos
7
- module Providers
8
- module Aws
9
-
10
- class UserManager
11
- include GemLogger::LoggerSupport
12
- include FileUtils
13
-
14
- def initialize(provider)
15
- @provider = provider
16
- end
17
-
18
- def create_user(user_name)
19
- result = {}
20
- client = ::Aws::IAM::Client.new
21
- resource = ::Aws::IAM::Resource.new
22
-
23
- user = resource.user(user_name)
24
-
25
- if user.exists?
26
- logger.info "User '#{user_name}' already exists"
27
- else
28
- logger.info "Creating new user '#{user_name}'"
29
- user = resource.create_user(user_name: user_name)
30
- client.wait_until(:user_exists, user_name: user_name)
31
- logger.debug "User created, user_name=#{user_name}"
32
- end
33
-
34
- result[:user_name] = user_name
35
-
36
- return result
37
- end
38
-
39
- def set_groups(user_name, groups, force: false)
40
- result = {}
41
- resource = ::Aws::IAM::Resource.new
42
-
43
- user = resource.user(user_name)
44
-
45
- existing_groups = user.groups.collect(&:name)
46
- groups_to_add = groups - existing_groups
47
- groups_to_remove = existing_groups - groups
48
-
49
- result[:groups] = existing_groups
50
-
51
- groups_to_add.each do |group|
52
- logger.debug "Adding group: #{group}"
53
- user.add_group(group_name: group)
54
- result[:groups] << group
55
- end
56
-
57
- if force
58
- groups_to_remove.each do |group|
59
- logger.debug "Removing group: #{group}"
60
- user.remove_group(group_name: group)
61
- result[:groups].delete(group)
62
- end
63
- end
64
-
65
- logger.info "User associated with groups=#{result[:groups]}"
66
-
67
- return result
68
- end
69
-
70
- def enable_login(user_name, force: false)
71
- result = {}
72
- resource = ::Aws::IAM::Resource.new
73
-
74
- user = resource.user(user_name)
75
-
76
- password = ""
77
- classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[!@#$%^&*()_+\-=\[\]{}|']/]
78
- while ! classes.all? {|c| password =~ c }
79
- password = SecureRandom.base64(15)
80
- end
81
-
82
- exists = false
83
- begin
84
- user.login_profile.create_date
85
- exists = true
86
- rescue ::Aws::IAM::Errors::NoSuchEntity
87
- exists = false
88
- end
89
-
90
- if exists
91
- logger.info "User login already exists"
92
- if force
93
- user.login_profile.update(password: password, password_reset_required: true)
94
- result[:password] = password
95
- logger.info "Updated user login with password=#{password}"
96
- end
97
- else
98
- user.create_login_profile(password: password, password_reset_required: true)
99
- result[:password] = password
100
- logger.info "User login enabled with password=#{password}"
101
- end
102
-
103
- return result
104
- end
105
-
106
- def enable_mfa(user_name, force: false)
107
- result = {}
108
- client = ::Aws::IAM::Client.new
109
- resource = ::Aws::IAM::Resource.new
110
-
111
- user = resource.user(user_name)
112
-
113
- if user.mfa_devices.first
114
- logger.info "User mfa devices already exist"
115
- if force
116
- logger.info "Deleting old mfa devices"
117
- user.mfa_devices.each do |dev|
118
- dev.disassociate
119
- client.delete_virtual_mfa_device(serial_number: dev.serial_number)
120
- Atmos::Otp.instance.remove(user_name)
121
- end
122
- else
123
- return result
124
- end
125
- end
126
-
127
- resp = client.create_virtual_mfa_device(
128
- virtual_mfa_device_name: user_name
129
- )
130
-
131
- serial = resp.virtual_mfa_device.serial_number
132
- seed = resp.virtual_mfa_device.base_32_string_seed
133
-
134
- Atmos::Otp.instance.add(user_name, seed)
135
- code1 = Atmos::Otp.instance.generate(user_name)
136
- interval = (30 - (Time.now.to_i % 30)) + 1
137
- logger.info "Waiting for #{interval}s to generate second otp key for enablement"
138
- sleep interval
139
- code2 = Atmos::Otp.instance.generate(user_name)
140
- raise "MFA codes should not be the same" if code1 == code2
141
-
142
- resp = client.enable_mfa_device({
143
- user_name: user_name,
144
- serial_number: serial,
145
- authentication_code_1: code1,
146
- authentication_code_2: code2,
147
- })
148
-
149
- result[:mfa_secret] = seed
150
-
151
- return result
152
- end
153
-
154
- def enable_access_keys(user_name, force: false)
155
- result = {}
156
- resource = ::Aws::IAM::Resource.new
157
-
158
- user = resource.user(user_name)
159
-
160
- if user.access_keys.first
161
- logger.info "User access keys already exist"
162
- if force
163
- logger.info "Deleting old access keys"
164
- user.access_keys.each do |key|
165
- key.delete
166
- end
167
- else
168
- return result
169
- end
170
- end
171
-
172
- # TODO: auto add to ~/.aws/credentials and config
173
- key_pair = user.create_access_key_pair
174
- result[:key] = key_pair.access_key_id
175
- result[:secret] = key_pair.secret
176
- logger.debug "User keys generated key=#{key_pair.access_key_id}, secret=#{key_pair.secret}"
177
-
178
- return result
179
- end
180
-
181
- def set_public_key(user_name, public_key, force: false)
182
- result = {}
183
- client = ::Aws::IAM::Client.new
184
- resource = ::Aws::IAM::Resource.new
185
-
186
- user = resource.user(user_name)
187
- keys = client.list_ssh_public_keys(user_name: user_name).ssh_public_keys
188
- if keys.size > 0
189
- logger.info "User ssh public keys already exist"
190
- if force
191
- logger.info "Deleting old ssh public keys"
192
- keys.each do |key|
193
- client.delete_ssh_public_key(user_name: user_name,
194
- ssh_public_key_id: key.ssh_public_key_id)
195
- end
196
- else
197
- return result
198
- end
199
- end
200
-
201
- client.upload_ssh_public_key(user_name: user_name, ssh_public_key_body: public_key)
202
- logger.debug "User public key assigned: #{public_key}"
203
-
204
- return result
205
- end
206
-
207
- end
208
-
209
- end
210
- end
211
- end
@@ -1,90 +0,0 @@
1
- require 'hashie'
2
-
3
- module Atmos
4
-
5
- class SettingsHash < Hashie::Mash
6
- include GemLogger::LoggerSupport
7
- include Hashie::Extensions::DeepMerge
8
- include Hashie::Extensions::DeepFetch
9
- disable_warnings
10
-
11
- PATH_PATTERN = /[\.\[\]]/
12
-
13
- def notation_get(key)
14
- path = key.to_s.split(PATH_PATTERN).compact
15
- path = path.collect {|p| p =~ /^\d+$/ ? p.to_i : p }
16
- result = nil
17
-
18
- begin
19
- result = deep_fetch(*path)
20
- rescue Hashie::Extensions::DeepFetch::UndefinedPathError => e
21
- logger.debug("Settings missing value for key='#{key}'")
22
- end
23
-
24
- return result
25
- end
26
-
27
- def notation_put(key, value, additive: true)
28
- path = key.to_s.split(PATH_PATTERN).compact
29
- path = path.collect {|p| p =~ /^\d+$/ ? p.to_i : p }
30
- current_level = self
31
- path.each_with_index do |p, i|
32
-
33
- if i == path.size - 1
34
- if additive && current_level[p].is_a?(Array)
35
- current_level[p] = current_level[p] | Array(value)
36
- else
37
- current_level[p] = value
38
- end
39
- else
40
- if current_level[p].nil?
41
- if path[i+1].is_a?(Integer)
42
- current_level[p] = []
43
- else
44
- current_level[p] = {}
45
- end
46
- end
47
- end
48
-
49
- current_level = current_level[p]
50
- end
51
- end
52
-
53
- def self.add_config(yml_file, key, value, additive: true)
54
- orig_config_with_comments = File.read(yml_file)
55
-
56
- comment_places = {}
57
- comment_lines = []
58
- orig_config_with_comments.each_line do |line|
59
- line.gsub!(/\s+$/, "\n")
60
- if line =~ /^\s*(#.*)?$/
61
- comment_lines << line
62
- else
63
- if comment_lines.present?
64
- comment_places[line.chomp] = comment_lines
65
- comment_lines = []
66
- end
67
- end
68
- end
69
- comment_places["<EOF>"] = comment_lines
70
-
71
- orig_config = SettingsHash.new((YAML.load_file(yml_file) rescue {}))
72
- orig_config.notation_put(key, value, additive: additive)
73
- new_config_no_comments = YAML.dump(orig_config.to_hash)
74
- new_config_no_comments.sub!(/\A---\n/, "")
75
-
76
- new_yml = ""
77
- new_config_no_comments.each_line do |line|
78
- line.gsub!(/\s+$/, "\n")
79
- cline = comment_places.keys.find {|k| line =~ /^#{k}/ }
80
- comments = comment_places[cline]
81
- comments.each {|comment| new_yml << comment } if comments
82
- new_yml << line
83
- end
84
- comment_places["<EOF>"].each {|comment| new_yml << comment }
85
-
86
- return new_yml
87
- end
88
-
89
- end
90
- end