subspace 2.5.10 → 3.0.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 +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
|