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