simplygenius-atmos 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|