subspace 2.5.10 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +22 -5
- data/README.md +105 -51
- data/UPGRADING.md +10 -0
- data/ansible/roles/common/defaults/main.yml +0 -1
- data/ansible/roles/common/files/sudoers-service +1 -1
- data/ansible/roles/common/tasks/main.yml +18 -7
- data/ansible/roles/common/tasks/no_swap.yml +26 -0
- data/ansible/roles/common/templates/motd +1 -1
- data/ansible/roles/common/templates/motd2 +1 -1
- data/ansible/roles/delayed_job/tasks/main.yml +21 -38
- data/ansible/roles/delayed_job/templates/delayed-job-systemd.service +33 -0
- data/ansible/roles/letsencrypt/defaults/main.yml +7 -7
- data/ansible/roles/letsencrypt/tasks/main.yml +18 -24
- data/ansible/roles/memcache/defaults/main.yml +2 -0
- data/ansible/roles/memcache/tasks/main.yml +16 -1
- data/ansible/roles/newrelic-infra/tasks/main.yml +3 -3
- data/ansible/roles/nginx/tasks/main.yml +12 -3
- data/ansible/roles/puma/tasks/main.yml +32 -20
- data/ansible/roles/puma/templates/puma-systemd.service +37 -0
- data/ansible/roles/puma/templates/puma-systemd.socket +14 -0
- data/ansible/roles/puma/templates/puma.rb +4 -2
- data/ansible/roles/rails/defaults/main.yml +0 -7
- data/ansible/roles/redis/tasks/main.yml +28 -3
- data/ansible/roles/resque/tasks/main.yml +11 -12
- data/ansible/roles/resque/templates/resque-systemd.service +10 -3
- data/ansible/roles/ruby-common/tasks/main.yml +1 -16
- data/ansible/roles/sidekiq/defaults/main.yml +1 -1
- data/ansible/roles/sidekiq/tasks/main.yml +11 -15
- data/ansible/roles/sidekiq/templates/sidekiq-monit-rc +1 -1
- data/ansible/roles/sidekiq/templates/sidekiq-systemd.service +63 -0
- data/ansible/roles/tailscale/defaults/main.yml +2 -0
- data/ansible/roles/tailscale/tasks/main.yml +22 -0
- data/bin/console +0 -4
- data/exe/subspace +1 -2
- data/lib/subspace/cli.rb +51 -14
- data/lib/subspace/commands/ansible.rb +12 -3
- data/lib/subspace/commands/base.rb +20 -5
- data/lib/subspace/commands/bootstrap.rb +16 -21
- data/lib/subspace/commands/configure.rb +2 -2
- data/lib/subspace/commands/exec.rb +20 -0
- data/lib/subspace/commands/init.rb +94 -45
- data/lib/subspace/commands/inventory.rb +54 -0
- data/lib/subspace/commands/maintain.rb +1 -1
- data/lib/subspace/commands/provision.rb +1 -3
- data/lib/subspace/commands/secrets.rb +69 -0
- data/lib/subspace/commands/ssh.rb +14 -8
- data/lib/subspace/commands/terraform.rb +83 -0
- data/lib/subspace/inventory.rb +144 -0
- data/lib/subspace/version.rb +1 -1
- data/subspace.gemspec +8 -2
- data/template/{provision → subspace}/.gitignore +3 -0
- data/template/{provision → subspace}/ansible.cfg.erb +2 -2
- data/template/subspace/group_vars/all.erb +28 -0
- data/template/subspace/group_vars/template.erb +26 -0
- data/template/{provision → subspace}/hosts.erb +0 -0
- data/template/subspace/inventory.yml.erb +11 -0
- data/template/{provision → subspace}/playbook.yml.erb +2 -5
- data/template/{provision/vars → subspace/secrets}/template.erb +0 -0
- data/template/{provision → subspace}/templates/application.yml.template +0 -0
- data/template/subspace/templates/authorized_keys.erb +1 -0
- data/template/subspace/terraform/.gitignore +2 -0
- data/template/subspace/terraform/template/main-oxenwagen.tf.erb +116 -0
- data/template/subspace/terraform/template/main-workhorse.tf.erb +41 -0
- data/template/subspace/terraformrc.erb +9 -0
- data/terraform/modules/s3_backend/README +2 -0
- data/terraform/modules/s3_backend/dynamodb.tf +1 -0
- data/terraform/modules/s3_backend/iam_user.tf +38 -0
- data/terraform/modules/s3_backend/main.tf +39 -0
- data/terraform/modules/s3_backend/state_bucket.tf +14 -0
- metadata +41 -55
- data/ansible/roles/awscli/tasks/main.yml +0 -10
- data/ansible/roles/delayed_job/meta/main.yml +0 -5
- data/ansible/roles/letsencrypt_dns/defaults/main.yml +0 -4
- data/ansible/roles/letsencrypt_dns/tasks/main.yml +0 -133
- data/ansible/roles/monit/files/monit-http.conf +0 -3
- data/ansible/roles/monit/files/sudoers-monit +0 -1
- data/ansible/roles/monit/handlers/main.yml +0 -14
- data/ansible/roles/monit/tasks/main.yml +0 -34
- data/ansible/roles/mtpereira.passenger/.bumpversion.cfg +0 -7
- data/ansible/roles/mtpereira.passenger/.gitignore +0 -2
- data/ansible/roles/mtpereira.passenger/LICENSE +0 -20
- data/ansible/roles/mtpereira.passenger/README.md +0 -31
- data/ansible/roles/mtpereira.passenger/defaults/main.yml +0 -5
- data/ansible/roles/mtpereira.passenger/handlers/main.yml +0 -8
- data/ansible/roles/mtpereira.passenger/meta/.galaxy_install_info +0 -1
- data/ansible/roles/mtpereira.passenger/meta/main.yml +0 -21
- data/ansible/roles/mtpereira.passenger/tasks/apt.yml +0 -13
- data/ansible/roles/mtpereira.passenger/tasks/main.yml +0 -8
- data/ansible/roles/mtpereira.passenger/tasks/pkg.yml +0 -35
- data/ansible/roles/mtpereira.passenger/tasks/service.yml +0 -8
- data/ansible/roles/passenger/files/sudoers-passenger +0 -1
- data/ansible/roles/passenger/meta/main.yml +0 -6
- data/ansible/roles/passenger/tasks/main.yml +0 -5
- data/ansible/roles/postgis/defaults/main.yml +0 -2
- data/ansible/roles/puma/defaults/main.yml +0 -5
- data/ansible/roles/puma/meta/main.yml +0 -5
- data/ansible/roles/sidekiq/meta/main.yml +0 -5
- data/lib/subspace/commands/vars.rb +0 -48
- data/template/provision/group_vars/all.erb +0 -17
- data/template/provision/group_vars/template.erb +0 -11
- data/template/provision/host_vars/template.erb +0 -4
@@ -1,19 +1,28 @@
|
|
1
1
|
module Subspace
|
2
2
|
module Commands
|
3
3
|
module Ansible
|
4
|
+
def ansible_playbook(*args)
|
5
|
+
args.push "--diff"
|
6
|
+
ansible_command("ansible-playbook", *args)
|
7
|
+
end
|
8
|
+
|
4
9
|
def ansible_command(command, *args)
|
5
10
|
update_ansible_cfg
|
6
|
-
|
11
|
+
retval = false
|
12
|
+
Dir.chdir "config/subspace" do
|
7
13
|
say ">> Running #{command} #{args.join(' ')}"
|
8
|
-
system(command, *args, out: $stdout, err: $stderr)
|
14
|
+
retval = system(command, *args, out: $stdout, err: $stderr)
|
9
15
|
say "<< Done"
|
10
16
|
end
|
17
|
+
retval
|
11
18
|
end
|
12
19
|
|
13
20
|
private
|
14
21
|
|
15
22
|
def update_ansible_cfg
|
16
|
-
if
|
23
|
+
if ENV["DISABLE_MITOGEN"]
|
24
|
+
puts "Mitogen explicitly disabled. Skipping detection. "
|
25
|
+
elsif `pip show mitogen 2>&1` =~ /^Location: (.*?)$/m
|
17
26
|
@mitogen_path = $1
|
18
27
|
puts "🏎🚀🚅Mitogen found at #{@mitogen_path}. WARP 9!....ENGAGE!🚀"
|
19
28
|
else
|
@@ -13,7 +13,7 @@ module Subspace
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def template_dir
|
16
|
-
File.join(gem_path, 'template', '
|
16
|
+
File.join(gem_path, 'template', 'subspace')
|
17
17
|
end
|
18
18
|
|
19
19
|
def gem_path
|
@@ -21,23 +21,34 @@ module Subspace
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def project_path
|
24
|
+
unless File.exist?(File.join(Dir.pwd, "config", "subspace"))
|
25
|
+
say "Subspace must be run from the project root"
|
26
|
+
exit
|
27
|
+
end
|
24
28
|
Dir.pwd # TODO make sure this is correct if they for whatever reason aren't running subspace from the project root??
|
25
29
|
end
|
26
30
|
|
31
|
+
def project_name
|
32
|
+
File.basename(project_path) # TODO see above, this should probably be in a configuration somewhere
|
33
|
+
end
|
34
|
+
|
27
35
|
def dest_dir
|
28
|
-
"config/
|
36
|
+
"config/subspace"
|
29
37
|
end
|
30
38
|
|
31
39
|
def template(src, dest = nil, render_binding = nil)
|
32
40
|
return unless confirm_overwrite File.join(dest_dir, dest || src)
|
33
41
|
template! src, dest, render_binding
|
34
|
-
say "Wrote #{dest}"
|
42
|
+
say "Wrote #{dest || src}"
|
35
43
|
end
|
36
44
|
|
37
45
|
def template!(src, dest = nil, render_binding = nil)
|
38
46
|
dest ||= src
|
39
|
-
template = ERB.new File.read(File.join(template_dir, "#{src}.erb")),
|
40
|
-
|
47
|
+
template = ERB.new File.read(File.join(template_dir, "#{src}.erb")), trim_mode: '-'
|
48
|
+
result = template.result(render_binding || binding)
|
49
|
+
|
50
|
+
|
51
|
+
File.write File.join(dest_dir, dest), result
|
41
52
|
end
|
42
53
|
|
43
54
|
def copy(src, dest = nil)
|
@@ -74,6 +85,10 @@ module Subspace
|
|
74
85
|
def set_subspace_version
|
75
86
|
ENV['SUBSPACE_VERSION'] = Subspace::VERSION
|
76
87
|
end
|
88
|
+
|
89
|
+
def inventory
|
90
|
+
@inventory ||= Subspace::Inventory.read("config/subspace/inventory.yml")
|
91
|
+
end
|
77
92
|
end
|
78
93
|
end
|
79
94
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class Subspace::Commands::Bootstrap < Subspace::Commands::Base
|
2
|
-
PASS_THROUGH_PARAMS = ["private-key"]
|
3
2
|
|
4
3
|
def initialize(args, options)
|
5
4
|
@host_spec = args.first
|
@@ -11,37 +10,33 @@ class Subspace::Commands::Bootstrap < Subspace::Commands::Base
|
|
11
10
|
|
12
11
|
def run
|
13
12
|
# ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"
|
14
|
-
|
15
|
-
|
13
|
+
hosts = inventory.find_hosts!(@host_spec)
|
14
|
+
update_ansible_cfg
|
15
|
+
hosts.each do |host|
|
16
|
+
say "Bootstapping #{host.vars["hostname"]}..."
|
17
|
+
learn_host(host)
|
18
|
+
install_python(host)
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
private
|
19
23
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
"-m",
|
24
|
-
"file",
|
25
|
-
"-a",
|
26
|
-
"path=/home/{{ansible_ssh_user}}/.ssh state=directory mode=0700",
|
27
|
-
"-vvvv"
|
28
|
-
]
|
29
|
-
cmd = cmd | pass_through_params
|
30
|
-
bootstrap_command cmd
|
24
|
+
def learn_host(host)
|
25
|
+
system "ssh-keygen -R #{host.vars["ansible_host"]}"
|
26
|
+
system "ssh-keyscan -H #{host.vars["ansible_host"]} >> ~/.ssh/known_hosts"
|
31
27
|
end
|
32
28
|
|
33
|
-
def install_python
|
34
|
-
update_ansible_cfg
|
29
|
+
def install_python(host)
|
35
30
|
cmd = ["ansible",
|
36
|
-
|
31
|
+
host.name,
|
32
|
+
"--private-key",
|
33
|
+
"config/subspace/subspace.pem",
|
37
34
|
"-m",
|
38
35
|
"raw",
|
39
36
|
"-a",
|
40
|
-
"test -e /usr/bin/
|
41
|
-
"--become"
|
42
|
-
"-vvvv"
|
37
|
+
"test -e /usr/bin/python3 || (apt -y update && apt install -y python3)",
|
38
|
+
"--become"
|
43
39
|
]
|
44
|
-
cmd = cmd | pass_through_params
|
45
40
|
bootstrap_command cmd
|
46
41
|
end
|
47
42
|
|
@@ -16,12 +16,12 @@ class Subspace::Commands::Configure < Subspace::Commands::Base
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def update_host_configuration(host)
|
19
|
-
say "Generating config/
|
19
|
+
say "Generating config/subspace/host_vars/#{host}"
|
20
20
|
template "host_vars/template", "host_vars/#{host}", Subspace.config.binding_for(host: host)
|
21
21
|
end
|
22
22
|
|
23
23
|
def update_group_configuration(group)
|
24
|
-
say "Generating config/
|
24
|
+
say "Generating config/subspace/group_vars/#{group}"
|
25
25
|
template "group_vars/template", "group_vars/#{group}", Subspace.config.binding_for(group: group)
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'subspace/inventory'
|
3
|
+
class Subspace::Commands::Exec < Subspace::Commands::Base
|
4
|
+
PASS_THROUGH_PARAMS = ["i"]
|
5
|
+
|
6
|
+
def initialize(args, options)
|
7
|
+
@host_spec = args[0]
|
8
|
+
@command = args[1]
|
9
|
+
@user = options.user
|
10
|
+
@options = options
|
11
|
+
run
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
hosts = inventory.find_hosts!(@host_spec)
|
16
|
+
|
17
|
+
say "> Running `#{@command}` on #{hosts.join ','}"
|
18
|
+
ansible_command "ansible", @host_spec, "-m", "command", "-a", @command
|
19
|
+
end
|
20
|
+
end
|
@@ -3,76 +3,113 @@ require 'erb'
|
|
3
3
|
require 'securerandom'
|
4
4
|
class Subspace::Commands::Init < Subspace::Commands::Base
|
5
5
|
def initialize(args, options)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
options.default env: "dev"
|
7
|
+
|
8
|
+
@env = options.env
|
9
|
+
@template = options.template
|
10
|
+
|
11
|
+
if options.ansibe.nil? && options.terraform.nil?
|
12
|
+
# They didn't pass in any options (subspace init) so just do both
|
13
|
+
options.ansible = true
|
14
|
+
options.terraform = true
|
15
|
+
end
|
16
|
+
|
17
|
+
if @template.nil? && options.terraform == true
|
18
|
+
answer = ask "What template/server configuration would you like to use? e.g. 'workhorse' or 'oxenwagen'"
|
19
|
+
@template = answer
|
10
20
|
end
|
21
|
+
|
22
|
+
@init_ansible = options.ansible
|
23
|
+
@init_terraform = options.terraform
|
24
|
+
run
|
11
25
|
end
|
12
26
|
|
13
27
|
def run
|
14
28
|
if File.exists? dest_dir
|
15
29
|
answer = ask "Subspace appears to be initialized. Reply 'yes' to continue anyway: [no] "
|
16
30
|
abort unless answer.chomp == "yes"
|
31
|
+
else
|
32
|
+
FileUtils.mkdir_p dest_dir
|
17
33
|
end
|
18
34
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
FileUtils.mkdir_p File.join dest_dir, "roles"
|
35
|
+
init_pemfile
|
36
|
+
init_ansible if @init_ansible
|
37
|
+
init_terraform if @init_terraform
|
23
38
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
39
|
+
puts """
|
40
|
+
1. Inspect key config files:
|
41
|
+
- config/subspace/terraform/#{@env}/main.tf # Main terraform file
|
42
|
+
- config/subspace/#{@env}.yml # Main ansible playbook
|
43
|
+
- config/subspace/group_vars # Ansible configuration options
|
44
|
+
- config/subspace/inventory.yml # Server Inventory
|
45
|
+
- config/subspace/templates/authorized_keys # SSH Authorized Keys
|
46
|
+
- config/subspace/templates/application.yml # Application Environment variables
|
29
47
|
|
30
|
-
|
31
|
-
environments.each do |env|
|
32
|
-
@env = env
|
33
|
-
@hostname = hostname(env)
|
34
|
-
template "group_vars/template", "group_vars/#{env}"
|
35
|
-
template "host_vars/template", "host_vars/#{env}"
|
36
|
-
create_vars_file_for_env env
|
37
|
-
template "playbook.yml", "#{env}.yml"
|
38
|
-
end
|
39
|
-
create_vars_file_for_env "development"
|
40
|
-
init_vars
|
48
|
+
2. create cloud infrastructure with terraform:
|
41
49
|
|
42
|
-
|
43
|
-
1. Create a server.
|
50
|
+
subspace tf #{@env}
|
44
51
|
|
45
|
-
|
52
|
+
3. Bootstrap the new server
|
46
53
|
|
47
|
-
|
48
|
-
vim config/provision/host_vars/dev
|
54
|
+
subspace boostrap #{@env}1
|
49
55
|
|
50
|
-
|
56
|
+
4. Inspect new environment
|
57
|
+
- ensure the correct roles are present in #{@env}.yml
|
58
|
+
- Check ansible configuration variables in group_vars/#{@env}
|
51
59
|
|
52
|
-
|
60
|
+
4. Provision the new servers with ansible:
|
53
61
|
|
54
|
-
|
62
|
+
subspace provision #{@env}
|
55
63
|
|
56
|
-
|
57
|
-
subspace provision production
|
64
|
+
!!MAKE SURE YOU PUT config/subspace/subspace.pem SOMEWHERE!!
|
58
65
|
|
59
66
|
"""
|
60
67
|
|
61
68
|
end
|
62
69
|
|
63
|
-
|
64
|
-
|
65
|
-
|
70
|
+
private
|
71
|
+
|
72
|
+
def init_pemfile
|
73
|
+
pem = File.join dest_dir, "subspace.pem"
|
74
|
+
if File.exist?(pem)
|
75
|
+
say "Existing SSH Keypair exists. Skipping keygen."
|
76
|
+
else
|
77
|
+
say "Creating SSH Keypair in #{pem}"
|
78
|
+
`ssh-keygen -t rsa -f #{pem}`
|
79
|
+
end
|
66
80
|
end
|
67
81
|
|
68
|
-
|
82
|
+
def init_ansible
|
83
|
+
FileUtils.mkdir_p File.join dest_dir, "group_vars"
|
84
|
+
FileUtils.mkdir_p File.join dest_dir, "secrets"
|
85
|
+
FileUtils.mkdir_p File.join dest_dir, "roles"
|
86
|
+
FileUtils.mkdir_p File.join dest_dir, "templates"
|
87
|
+
|
88
|
+
copy ".gitignore"
|
89
|
+
template "ansible.cfg"
|
90
|
+
template "group_vars/all"
|
91
|
+
template "inventory.yml"
|
92
|
+
template "templates/authorized_keys"
|
69
93
|
|
70
|
-
|
71
|
-
|
94
|
+
create_vault_pass
|
95
|
+
@hostname = hostname(@env)
|
96
|
+
create_secrets_for @env
|
97
|
+
template "group_vars/template", "group_vars/#{@env}"
|
98
|
+
template "playbook.yml", "#{@env}.yml"
|
99
|
+
|
100
|
+
create_secrets_for "development" #TODO rename to local?
|
101
|
+
init_appyml
|
72
102
|
end
|
73
103
|
|
74
|
-
def
|
75
|
-
|
104
|
+
def init_terraform
|
105
|
+
Subspace::Commands::Terraform.ensure_terraform
|
106
|
+
Subspace::Commands::Terraform.check_aws_credentials(project_name)
|
107
|
+
|
108
|
+
FileUtils.mkdir_p File.join dest_dir, "terraform", @env
|
109
|
+
|
110
|
+
set_latest_ami
|
111
|
+
template "terraform/template/main-#{@template}.tf", "terraform/#{@env}/main.tf"
|
112
|
+
copy "terraform/.gitignore"
|
76
113
|
end
|
77
114
|
|
78
115
|
def hostname(env)
|
@@ -87,10 +124,22 @@ class Subspace::Commands::Init < Subspace::Commands::Base
|
|
87
124
|
end
|
88
125
|
end
|
89
126
|
|
90
|
-
def
|
91
|
-
template "
|
127
|
+
def create_secrets_for(env)
|
128
|
+
template "secrets/template", "secrets/#{env}.yml"
|
92
129
|
Dir.chdir dest_dir do
|
93
|
-
`ansible-vault encrypt
|
130
|
+
`ansible-vault encrypt secrets/#{env}.yml`
|
94
131
|
end
|
95
132
|
end
|
133
|
+
|
134
|
+
def init_appyml
|
135
|
+
copy "templates/application.yml.template"
|
136
|
+
end
|
137
|
+
|
138
|
+
def set_latest_ami
|
139
|
+
@latest_ami = `aws --profile subspace-#{project_name} ec2 describe-images \
|
140
|
+
--filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64*' \
|
141
|
+
--query 'Images[*].[ImageId,CreationDate]' --output text \
|
142
|
+
| sort -k2 -r \
|
143
|
+
| head -n1 | cut -f1`.chomp
|
144
|
+
end
|
96
145
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Subspace::Commands::Inventory < Subspace::Commands::Base
|
2
|
+
|
3
|
+
def initialize(args, options)
|
4
|
+
command = args.first
|
5
|
+
@env = options.env
|
6
|
+
case command
|
7
|
+
when "capistrano"
|
8
|
+
capistrano_deployrb
|
9
|
+
when "list"
|
10
|
+
list_inventory
|
11
|
+
when "keyscan"
|
12
|
+
keyscan_inventory
|
13
|
+
else
|
14
|
+
say "Unknown or missing command to inventory: #{command}"
|
15
|
+
say "try subspace inventory [list, capistrano]"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def list_inventory
|
20
|
+
inventory.find_hosts!(@env || "all").each do |host|
|
21
|
+
puts "#{host.name}\t#{host.vars["ansible_host"]}\t(#{host.group_list.join ','})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def keyscan_inventory
|
26
|
+
inventory.find_hosts!(@env || "all").each do |host|
|
27
|
+
ip = host.vars["ansible_host"]
|
28
|
+
system %Q(ssh-keygen -R #{ip})
|
29
|
+
system %Q(ssh-keyscan -Ht ed25519 #{ip} >> "$HOME/.ssh/known_hosts")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def capistrano_deployrb
|
34
|
+
if @env.nil?
|
35
|
+
puts "Please provide an environment e.g: subspace inventory capistrano --env production"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
say "# config/deploy/#{@env}.rb"
|
40
|
+
say "# Generated by Subspace"
|
41
|
+
inventory.find_hosts!(@env).each do |host|
|
42
|
+
host = inventory.hosts[host.name]
|
43
|
+
db_role = false
|
44
|
+
roles = host.group_list.map do |group_name|
|
45
|
+
if group_name =~ /web/
|
46
|
+
["web", "app"]
|
47
|
+
elsif group_name =~ /worker/
|
48
|
+
["app", db_role ? nil : "db"]
|
49
|
+
end
|
50
|
+
end.compact.uniq
|
51
|
+
say "server '#{host.vars["ansible_host"]}', user: 'deploy', roles: %w{#{roles.join(' ')}} # #{host.vars["hostname"]}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -11,6 +11,6 @@ class Subspace::Commands::Maintain < Subspace::Commands::Base
|
|
11
11
|
def run
|
12
12
|
ansible_options = ["--diff", "--tags=maintenance"]
|
13
13
|
ansible_options = ansible_options | pass_through_params
|
14
|
-
|
14
|
+
ansible_playbook "#{@environment}.yml", *ansible_options
|
15
15
|
end
|
16
16
|
end
|
@@ -9,8 +9,6 @@ class Subspace::Commands::Provision < Subspace::Commands::Base
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def run
|
12
|
-
|
13
|
-
ansible_options = ansible_options | pass_through_params
|
14
|
-
ansible_command "ansible-playbook", "#{@environment}.yml", *ansible_options
|
12
|
+
ansible_playbook "#{@environment}.yml", *pass_through_params
|
15
13
|
end
|
16
14
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Subspace::Commands::Secrets < Subspace::Commands::Base
|
2
|
+
def initialize(args, options)
|
3
|
+
if args.first == "rekey"
|
4
|
+
rekey
|
5
|
+
else
|
6
|
+
@environment = args.first
|
7
|
+
@action = if options.edit
|
8
|
+
"edit"
|
9
|
+
elsif options.create
|
10
|
+
"create"
|
11
|
+
else
|
12
|
+
"view"
|
13
|
+
end
|
14
|
+
|
15
|
+
run
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
update_ansible_cfg
|
21
|
+
case @action
|
22
|
+
when "create"
|
23
|
+
create_local
|
24
|
+
when "view", "edit"
|
25
|
+
ansible_command "ansible-vault", @action, "secrets/#{@environment}.yml"
|
26
|
+
else
|
27
|
+
abort "Invalid secrets command"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_local
|
32
|
+
if File.exists? File.join(project_path, "config/application.yml")
|
33
|
+
answer = ask "config/application.yml already exists. Reply 'yes' to overwrite: [no] "
|
34
|
+
abort unless answer == "yes"
|
35
|
+
end
|
36
|
+
src = application_yml_template
|
37
|
+
dest = "config/application.yml"
|
38
|
+
vars_file = File.join(project_path, dest_dir, "/secrets/#{@environment}.yml")
|
39
|
+
extra_vars = "project_path=#{project_path} vars_file=#{vars_file} src=#{src} dest=#{dest}"
|
40
|
+
ansible_command "ansible-playbook", File.join(playbook_dir, "local_template.yml"), "--extra-vars", extra_vars
|
41
|
+
say "File created at config/application.yml with #{@environment} secrets"
|
42
|
+
say "-------------------------------------------------------------------\n"
|
43
|
+
|
44
|
+
system "cat", "config/application.yml"
|
45
|
+
end
|
46
|
+
|
47
|
+
def rekey
|
48
|
+
secret_files = Dir.glob("config/subspace/secrets/*.yml").map {|x| "secrets/#{File.basename(x)}"}
|
49
|
+
exit unless agree("This will re-key your secrets with a new random vault_pass. (#{secret_files}). Proceed? (yes to continue) ")
|
50
|
+
|
51
|
+
|
52
|
+
say "Writing new password to .vault_pass.new"
|
53
|
+
File.write "config/subspace/.vault_pass.new", SecureRandom.base64(24) + "\n"
|
54
|
+
success = ansible_command "ansible-vault", "rekey", "--vault-password-file", ".vault_pass", "--new-vault-password-file", ".vault_pass.new", "-v", *secret_files
|
55
|
+
if success
|
56
|
+
FileUtils.mv "config/subspace/.vault_pass", "config/subspace/.vault_pass.old"
|
57
|
+
FileUtils.mv "config/subspace/.vault_pass.new", "config/subspace/.vault_pass"
|
58
|
+
else
|
59
|
+
say "Something went wrong, not changing .vault_pass"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def application_yml_template
|
66
|
+
"#{dest_dir}/templates/application.yml.template"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'subspace/inventory'
|
2
3
|
class Subspace::Commands::Ssh < Subspace::Commands::Base
|
3
4
|
PASS_THROUGH_PARAMS = ["i"]
|
4
5
|
|
@@ -10,18 +11,23 @@ class Subspace::Commands::Ssh < Subspace::Commands::Base
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def run
|
13
|
-
if !
|
14
|
+
if !inventory.hosts[@host]
|
14
15
|
say "No host '#{@host}' found. "
|
15
|
-
all_hosts =
|
16
|
+
all_hosts = inventory.hosts.keys
|
16
17
|
say (["Available hosts:"] + all_hosts).join("\n\t")
|
17
18
|
return
|
18
19
|
end
|
19
|
-
host_vars =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
host_vars = inventory.hosts[@host].vars
|
21
|
+
if host_vars.key?('ansible_ssh_user')
|
22
|
+
say "Supposed to be ansible_user not ansible_ssh_user"
|
23
|
+
end
|
24
|
+
user = @user || host_vars["ansible_user"]
|
25
|
+
host = host_vars["ansible_host"]
|
26
|
+
port = host_vars["ansible_port"] || 22
|
27
|
+
pem = host_vars["ansible_ssh_private_key_file"]
|
28
|
+
pem_cmd = "-i config/subspace/#{pem}" if pem
|
29
|
+
cmd = "ssh #{user}@#{host} -p #{port} #{pem_cmd} #{pass_through_params.join(" ")}"
|
30
|
+
say "> #{cmd} \n"
|
25
31
|
exec cmd
|
26
32
|
end
|
27
33
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Subspace
|
3
|
+
module Commands
|
4
|
+
class Terraform < Subspace::Commands::Base
|
5
|
+
|
6
|
+
def self.check_aws_credentials(project_name)
|
7
|
+
ENV["AWS_ACCESS_KEY_ID"] = nil
|
8
|
+
ENV["AWS_SECRET_ACCESS_KEY"] = nil
|
9
|
+
|
10
|
+
profile = "subspace-#{project_name}"
|
11
|
+
|
12
|
+
system("aws --profile #{profile} configure list &> /dev/null ")
|
13
|
+
if $? != 0
|
14
|
+
puts "No AWS Profile '#{profile}' configured. Please enter your credentials."
|
15
|
+
system("aws --profile #{profile} configure")
|
16
|
+
system("aws --profile #{profile} configure list &> /dev/null ")
|
17
|
+
if $? != 0
|
18
|
+
puts "FATAL: could not configure aws. Please try again"
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
else
|
22
|
+
puts "Using AWS Profile #{profile}"
|
23
|
+
end
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.ensure_terraform
|
28
|
+
if `terraform -v --json | jq -r .terraform_version` =~ /1\.\d+/
|
29
|
+
puts "Terraform found."
|
30
|
+
return true
|
31
|
+
else
|
32
|
+
puts "Please install terraform at least 1.1 locally"
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(args, options)
|
38
|
+
@env = args.shift
|
39
|
+
@args = args
|
40
|
+
@options = options
|
41
|
+
run
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
self.class.check_aws_credentials(project_name) or exit
|
46
|
+
self.class.ensure_terraform or exit
|
47
|
+
if @args.any?
|
48
|
+
terraform_command(@args.shift, *@args)
|
49
|
+
else
|
50
|
+
puts "No command specified, running plan/apply"
|
51
|
+
terraform_command("init", "-upgrade") or return
|
52
|
+
terraform_command("apply") or return
|
53
|
+
update_inventory
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def terraform_command(command, *args)
|
59
|
+
result = nil
|
60
|
+
# update_terraformrc
|
61
|
+
Dir.chdir "config/subspace/terraform/#{@env}" do
|
62
|
+
say ">> Running terraform #{command} #{args.join(' ')}"
|
63
|
+
result = system("terraform", command, *args, out: $stdout, err: $stderr)
|
64
|
+
say "<< Done"
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_terraformrc
|
70
|
+
template! "terraformrc", ".terraformrc"
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_inventory
|
74
|
+
puts "Apply succeeded, updating inventory."
|
75
|
+
Dir.chdir "config/subspace/terraform/#{@env}" do
|
76
|
+
@output = JSON.parse `terraform output -json inventory`
|
77
|
+
end
|
78
|
+
inventory.merge(@output)
|
79
|
+
inventory.write
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|