simplygenius-atmos 0.7.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +212 -0
- data/exe/atmos +4 -0
- data/exe/atmos-docker +12 -0
- data/lib/atmos.rb +12 -0
- data/lib/atmos/cli.rb +105 -0
- data/lib/atmos/commands/account.rb +65 -0
- data/lib/atmos/commands/apply.rb +20 -0
- data/lib/atmos/commands/auth_exec.rb +29 -0
- data/lib/atmos/commands/base_command.rb +12 -0
- data/lib/atmos/commands/bootstrap.rb +72 -0
- data/lib/atmos/commands/container.rb +58 -0
- data/lib/atmos/commands/destroy.rb +18 -0
- data/lib/atmos/commands/generate.rb +90 -0
- data/lib/atmos/commands/init.rb +18 -0
- data/lib/atmos/commands/new.rb +18 -0
- data/lib/atmos/commands/otp.rb +54 -0
- data/lib/atmos/commands/plan.rb +20 -0
- data/lib/atmos/commands/secret.rb +87 -0
- data/lib/atmos/commands/terraform.rb +52 -0
- data/lib/atmos/commands/user.rb +74 -0
- data/lib/atmos/config.rb +208 -0
- data/lib/atmos/exceptions.rb +9 -0
- data/lib/atmos/generator.rb +199 -0
- data/lib/atmos/generator_factory.rb +93 -0
- data/lib/atmos/ipc.rb +132 -0
- data/lib/atmos/ipc_actions/notify.rb +27 -0
- data/lib/atmos/ipc_actions/ping.rb +19 -0
- data/lib/atmos/logging.rb +160 -0
- data/lib/atmos/otp.rb +61 -0
- data/lib/atmos/provider_factory.rb +19 -0
- data/lib/atmos/providers/aws/account_manager.rb +82 -0
- data/lib/atmos/providers/aws/auth_manager.rb +208 -0
- data/lib/atmos/providers/aws/container_manager.rb +116 -0
- data/lib/atmos/providers/aws/provider.rb +51 -0
- data/lib/atmos/providers/aws/s3_secret_manager.rb +49 -0
- data/lib/atmos/providers/aws/user_manager.rb +211 -0
- data/lib/atmos/settings_hash.rb +90 -0
- data/lib/atmos/terraform_executor.rb +267 -0
- data/lib/atmos/ui.rb +159 -0
- data/lib/atmos/utils.rb +50 -0
- data/lib/atmos/version.rb +3 -0
- data/templates/new/config/atmos.yml +50 -0
- data/templates/new/config/atmos/runtime.yml +43 -0
- data/templates/new/templates.yml +1 -0
- metadata +526 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require 'climate_control'
|
3
|
+
|
4
|
+
module Atmos::Commands
|
5
|
+
|
6
|
+
class AuthExec < BaseCommand
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Exec subprocess with an authenticated environment"
|
10
|
+
end
|
11
|
+
|
12
|
+
option ["-r", "--role"],
|
13
|
+
'ROLE', "overrides assume role name\n"
|
14
|
+
|
15
|
+
parameter "COMMAND ...", "command to exec", :attribute_name => :command
|
16
|
+
|
17
|
+
def execute
|
18
|
+
Atmos.config.provider.auth_manager.authenticate(ENV, role: role) do |auth_env|
|
19
|
+
result = system(auth_env, *command)
|
20
|
+
if ! result
|
21
|
+
logger.error("Process failed: #{command}")
|
22
|
+
exit(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
|
3
|
+
module Atmos::Commands
|
4
|
+
|
5
|
+
class Bootstrap < BaseCommand
|
6
|
+
|
7
|
+
def self.description
|
8
|
+
"Sets up the initial aws account for use by atmos"
|
9
|
+
end
|
10
|
+
|
11
|
+
option ["-f", "--force"],
|
12
|
+
:flag, "forces bootstrap\n"
|
13
|
+
|
14
|
+
def execute
|
15
|
+
|
16
|
+
tf_init_dir = File.join(Atmos.config.tf_working_dir('bootstrap'), '.terraform')
|
17
|
+
tf_initialized = File.exist?(tf_init_dir)
|
18
|
+
backend_initialized = File.exist?(File.join(tf_init_dir, 'terraform.tfstate'))
|
19
|
+
|
20
|
+
rebootstrap_msg = <<~EOF
|
21
|
+
Bootstrap should only be performed when provisioning an account for the first
|
22
|
+
time. Try 'atmos terraform init'
|
23
|
+
EOF
|
24
|
+
|
25
|
+
if !force? && tf_initialized
|
26
|
+
signal_usage_error(rebootstrap_msg)
|
27
|
+
end
|
28
|
+
|
29
|
+
Atmos.config.provider.auth_manager.authenticate(ENV, bootstrap: true) do |auth_env|
|
30
|
+
begin
|
31
|
+
exe = Atmos::TerraformExecutor.new(process_env: auth_env, working_group: 'bootstrap')
|
32
|
+
|
33
|
+
skip_backend = true
|
34
|
+
skip_secrets = true
|
35
|
+
if backend_initialized
|
36
|
+
skip_backend = false
|
37
|
+
skip_secrets = false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Cases
|
41
|
+
# 1) bootstrap of new account - success
|
42
|
+
# 2) repeating bootstrap of new account due to failure partway - success
|
43
|
+
# 3) try to rebootstrap existing account on fresh checkout - should fail trying to create resources of same name, check output for this?
|
44
|
+
# 4) bootstrap new account with no-default secrets
|
45
|
+
|
46
|
+
# Need to init before we can create the resources to store state in bootstrap
|
47
|
+
exe.run("init", "-input=false", "-lock=false",
|
48
|
+
skip_backend: true, skip_secrets: true)
|
49
|
+
|
50
|
+
# Bootstrap to create the resources needed to store state
|
51
|
+
exe.run("apply", "-input=false",
|
52
|
+
skip_backend: true, skip_secrets: true)
|
53
|
+
|
54
|
+
# Need to init to setup the backend state after we create the resources
|
55
|
+
# to store state in bootstrap
|
56
|
+
exe.run("init", "-input=false", "-force-copy", skip_secrets: true)
|
57
|
+
|
58
|
+
# Might as well init the non-bootstrap case as well once the state
|
59
|
+
# storage has been setup in bootstrap
|
60
|
+
exe = Atmos::TerraformExecutor.new(process_env: auth_env)
|
61
|
+
exe.run("init", "-input=false", skip_secrets: true)
|
62
|
+
|
63
|
+
rescue Atmos::TerraformExecutor::ProcessFailed => e
|
64
|
+
logger.error(e.message)
|
65
|
+
logger.error(rebootstrap_msg)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require_relative '../../atmos/settings_hash'
|
3
|
+
require 'climate_control'
|
4
|
+
|
5
|
+
module Atmos::Commands
|
6
|
+
|
7
|
+
class Container < BaseCommand
|
8
|
+
|
9
|
+
def self.description
|
10
|
+
"Manages containers in the cloud provider"
|
11
|
+
end
|
12
|
+
|
13
|
+
subcommand "deploy", "Deploy a container" do
|
14
|
+
|
15
|
+
option ["-c", "--cluster"],
|
16
|
+
"CLUSTER", "The cluster name\n",
|
17
|
+
required: true
|
18
|
+
|
19
|
+
option ["-r", "--role"],
|
20
|
+
"ROLE", "The role to assume when deploying\n"
|
21
|
+
|
22
|
+
option ["-i", "--image"],
|
23
|
+
"IMAGE", "The local container image to deploy\nDefaults to service/task name"
|
24
|
+
|
25
|
+
option ["-t", "--task"],
|
26
|
+
:flag, "Deploy as a task, not a service\n"
|
27
|
+
|
28
|
+
option ["-v", "--revision"],
|
29
|
+
"REVISION", "Use as the remote image revision\n"
|
30
|
+
|
31
|
+
parameter "NAME",
|
32
|
+
"The name of the service (or task) to deploy"
|
33
|
+
|
34
|
+
def default_image
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute
|
39
|
+
Atmos.config.provider.auth_manager.authenticate(ENV, role: role) do |auth_env|
|
40
|
+
ClimateControl.modify(auth_env) do
|
41
|
+
mgr = Atmos.config.provider.container_manager
|
42
|
+
|
43
|
+
result = mgr.push(name, image, revision: revision)
|
44
|
+
if task?
|
45
|
+
result = result.merge(mgr.deploy_task(name, result[:remote_image]))
|
46
|
+
else
|
47
|
+
result = result.merge(mgr.deploy_service(cluster, name, result[:remote_image]))
|
48
|
+
end
|
49
|
+
|
50
|
+
logger.info "Container deployed:\n #{display result}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'terraform'
|
2
|
+
|
3
|
+
module Atmos::Commands
|
4
|
+
|
5
|
+
class Destroy < Atmos::Commands::Terraform
|
6
|
+
|
7
|
+
def self.description
|
8
|
+
"Runs terraform destroy"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@terraform_arguments.insert(0, "destroy")
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require_relative '../../atmos/generator_factory'
|
3
|
+
require_relative '../../atmos/utils'
|
4
|
+
|
5
|
+
module Atmos::Commands
|
6
|
+
|
7
|
+
# From https://github.com/rubber/rubber/blob/master/lib/rubber/commands/vulcanize.rb
|
8
|
+
class Generate < BaseCommand
|
9
|
+
|
10
|
+
def self.description
|
11
|
+
<<~EOF
|
12
|
+
Installs configuration templates used by atmos to create infrastructure
|
13
|
+
resources e.g.
|
14
|
+
|
15
|
+
atmos generate aws-vpc
|
16
|
+
|
17
|
+
use --list to get a list of the template names for a given sourceroot
|
18
|
+
EOF
|
19
|
+
end
|
20
|
+
|
21
|
+
option ["-f", "--force"],
|
22
|
+
:flag, "Overwrite files that already exist"
|
23
|
+
option ["-n", "--dryrun"],
|
24
|
+
:flag, "Run but do not make any changes"
|
25
|
+
option ["-q", "--quiet"],
|
26
|
+
:flag, "Supress status output"
|
27
|
+
option ["-s", "--skip"],
|
28
|
+
:flag, "Skip files that already exist"
|
29
|
+
option ["-d", "--[no-]dependencies"],
|
30
|
+
:flag, "Walk dependencies, or not", default: true
|
31
|
+
option ["-l", "--list"],
|
32
|
+
:flag, "list available templates\n"
|
33
|
+
option ["-p", "--sourcepath"],
|
34
|
+
"PATH", "find templates at given path or github url\n",
|
35
|
+
multivalued: true
|
36
|
+
|
37
|
+
parameter "TEMPLATE ...", "atmos template(s)", required: false
|
38
|
+
|
39
|
+
def execute
|
40
|
+
signal_usage_error "template name is required" if template_list.blank? && ! list?
|
41
|
+
|
42
|
+
# don't want to fail for new repo
|
43
|
+
if Atmos.config && Atmos.config.is_atmos_repo?
|
44
|
+
config_sourcepaths = Atmos.config['template_sources'].try(:collect, &:location) || []
|
45
|
+
sourcepath_list.concat(config_sourcepaths)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Always search for templates against the bundled templates directory
|
49
|
+
sourcepath_list << File.expand_path('../../../../templates', __FILE__)
|
50
|
+
|
51
|
+
g = Atmos::GeneratorFactory.create(sourcepath_list,
|
52
|
+
force: force?,
|
53
|
+
pretend: dryrun?,
|
54
|
+
quiet: quiet?,
|
55
|
+
skip: skip?,
|
56
|
+
dependencies: dependencies?)
|
57
|
+
if list?
|
58
|
+
logger.info "Valid templates are:"
|
59
|
+
list_templates(g, template_list).each {|l| logger.info(l) }
|
60
|
+
else
|
61
|
+
g.generate(template_list)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def list_templates(generator, name_filters)
|
67
|
+
# Format templates into comma-separated paragraph with limt of 70 characters per line
|
68
|
+
filtered_names = generator.valid_templates.select do |name|
|
69
|
+
name_filters.blank? || name_filters.any? {|f| name =~ /#{f}/ }
|
70
|
+
end
|
71
|
+
|
72
|
+
lines = ['']
|
73
|
+
filtered_names.each do |template_name|
|
74
|
+
line = lines.last
|
75
|
+
if line.size == 0
|
76
|
+
line << template_name
|
77
|
+
elsif line.size + template_name.size > 68
|
78
|
+
line << ','
|
79
|
+
lines << template_name # new line
|
80
|
+
else
|
81
|
+
line << ", " + template_name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
return lines
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'terraform'
|
2
|
+
|
3
|
+
module Atmos::Commands
|
4
|
+
|
5
|
+
class Init < Atmos::Commands::Terraform
|
6
|
+
|
7
|
+
def self.description
|
8
|
+
"Runs terraform init"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@terraform_arguments.insert(0, "init")
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'generate'
|
2
|
+
|
3
|
+
module Atmos::Commands
|
4
|
+
|
5
|
+
class New < Atmos::Commands::Generate
|
6
|
+
|
7
|
+
def self.description
|
8
|
+
"Sets up a new atmos project in the current directory"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
template_list << "new"
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require_relative '../../atmos/otp'
|
3
|
+
require 'clipboard'
|
4
|
+
|
5
|
+
module Atmos::Commands
|
6
|
+
|
7
|
+
class Otp < BaseCommand
|
8
|
+
|
9
|
+
def self.description
|
10
|
+
"Generates an otp token for the given user"
|
11
|
+
end
|
12
|
+
|
13
|
+
option ["-s", "--secret"],
|
14
|
+
'SECRET', "The otp secret\nWill save for future use"
|
15
|
+
|
16
|
+
option ["-c", "--clipboard"],
|
17
|
+
:flag,
|
18
|
+
<<~EOF
|
19
|
+
Automatically copy the token to the system
|
20
|
+
clipboard. For dependencies see:
|
21
|
+
https://github.com/janlelis/clipboard
|
22
|
+
EOF
|
23
|
+
|
24
|
+
parameter "NAME",
|
25
|
+
"The otp name (IAM username)"
|
26
|
+
|
27
|
+
def execute
|
28
|
+
code = nil
|
29
|
+
if secret
|
30
|
+
Atmos::Otp.instance.add(name, secret)
|
31
|
+
code = Atmos::Otp.instance.generate(name)
|
32
|
+
Atmos::Otp.instance.save
|
33
|
+
else
|
34
|
+
code = Atmos::Otp.instance.generate(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
if code.nil?
|
38
|
+
signal_usage_error <<~EOF
|
39
|
+
No otp secret has been setup for #{name}
|
40
|
+
Use the -m flag to 'atmos user create' to create/activate one
|
41
|
+
or associate an existing secret with 'atmos otp -s <secret> <name>'
|
42
|
+
EOF
|
43
|
+
else
|
44
|
+
puts code
|
45
|
+
end
|
46
|
+
|
47
|
+
if clipboard?
|
48
|
+
Clipboard.copy(code)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'terraform'
|
2
|
+
|
3
|
+
module Atmos::Commands
|
4
|
+
|
5
|
+
class Plan < Atmos::Commands::Terraform
|
6
|
+
|
7
|
+
def self.description
|
8
|
+
"Runs terraform plan"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
args = ["plan"]
|
13
|
+
args << "--get-modules" unless Atmos.config["disable_auto_modules"].to_s == "true"
|
14
|
+
@terraform_arguments.insert(0, *args)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative 'base_command'
|
2
|
+
require 'climate_control'
|
3
|
+
|
4
|
+
module Atmos::Commands
|
5
|
+
|
6
|
+
class Secret < BaseCommand
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
"Manages application secrets"
|
10
|
+
end
|
11
|
+
|
12
|
+
subcommand "get", "Gets the secret value" do
|
13
|
+
|
14
|
+
parameter "KEY",
|
15
|
+
"The secret key"
|
16
|
+
|
17
|
+
def execute
|
18
|
+
|
19
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
20
|
+
ClimateControl.modify(auth_env) do
|
21
|
+
value = Atmos.config.provider.secret_manager.get(key)
|
22
|
+
logger.info "Secret value for #{key}: #{value}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
subcommand "set", "Sets the secret value" do
|
31
|
+
|
32
|
+
parameter "KEY",
|
33
|
+
"The secret key"
|
34
|
+
|
35
|
+
parameter "VALUE",
|
36
|
+
"The secret value"
|
37
|
+
|
38
|
+
def execute
|
39
|
+
|
40
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
41
|
+
ClimateControl.modify(auth_env) do
|
42
|
+
Atmos.config.provider.secret_manager.set(key, value)
|
43
|
+
logger.info "Secret set for #{key}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
subcommand "list", "Lists all secret keys" do
|
52
|
+
|
53
|
+
def execute
|
54
|
+
|
55
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
56
|
+
ClimateControl.modify(auth_env) do
|
57
|
+
logger.info "Secret keys are:"
|
58
|
+
Atmos.config.provider.secret_manager.to_h.keys.each {|k| logger.info k}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
subcommand "delete", "Deletes the secret key/value" do
|
67
|
+
|
68
|
+
parameter "KEY",
|
69
|
+
"The secret key"
|
70
|
+
|
71
|
+
def execute
|
72
|
+
|
73
|
+
Atmos.config.provider.auth_manager.authenticate(ENV) do |auth_env|
|
74
|
+
ClimateControl.modify(auth_env) do
|
75
|
+
value = Atmos.config.provider.secret_manager.get(key)
|
76
|
+
Atmos.config.provider.secret_manager.delete(key)
|
77
|
+
logger.info "Deleted secret: #{key}=#{value}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|