subspace 2.5.10 → 3.0.0.rc1
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 +12 -5
- data/README.md +57 -24
- 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 +1 -1
- 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 +36 -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 +7 -0
- data/ansible/roles/resque/tasks/main.yml +11 -12
- data/ansible/roles/resque/templates/resque-systemd.service +10 -3
- data/ansible/roles/ruby-common/README.md +1 -1
- data/ansible/roles/ruby-common/tasks/main.yml +2 -17
- 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 +62 -0
- data/ansible/roles/tailscale/defaults/main.yml +2 -0
- data/ansible/roles/tailscale/tasks/main.yml +22 -0
- data/exe/subspace +1 -2
- data/lib/subspace/cli.rb +50 -14
- data/lib/subspace/commands/ansible.rb +11 -2
- 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 +45 -0
- data/lib/subspace/commands/maintain.rb +1 -1
- data/lib/subspace/commands/provision.rb +1 -3
- data/lib/subspace/commands/{vars.rb → secrets.rb} +6 -5
- data/lib/subspace/commands/ssh.rb +10 -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/subspace/inventory.yml.erb +11 -0
- data/template/{provision → subspace}/playbook.yml.erb +2 -5
- 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 +42 -53
- 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/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
- /data/template/{provision → subspace}/hosts.erb +0 -0
- /data/template/{provision/vars → subspace/secrets}/template.erb +0 -0
- /data/template/{provision → subspace}/templates/application.yml.template +0 -0
|
@@ -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,45 @@
|
|
|
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
|
+
else
|
|
12
|
+
say "Unknown or missing command to inventory: #{command}"
|
|
13
|
+
say "try subspace inventory [list, capistrano]"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def list_inventory
|
|
18
|
+
inventory.find_hosts!(@env || "all").each do |host_name|
|
|
19
|
+
host = inventory.hosts[host_name]
|
|
20
|
+
puts "#{host.name}\t#{host.vars["ansible_host"]}\t(#{host.group_list.join ','})"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def capistrano_deployrb
|
|
25
|
+
if @env.nil?
|
|
26
|
+
puts "Please provide an environment e.g: subspace inventory capistrano --env production"
|
|
27
|
+
exit
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
say "# config/deploy/#{@env}.rb"
|
|
31
|
+
say "# Generated by Subspace"
|
|
32
|
+
inventory.find_hosts!(@env).each do |host_name|
|
|
33
|
+
host = inventory.hosts[host_name]
|
|
34
|
+
db_role = false
|
|
35
|
+
roles = host.group_list.map do |group_name|
|
|
36
|
+
if group_name =~ /web/
|
|
37
|
+
["web", "app"]
|
|
38
|
+
elsif group_name =~ /worker/
|
|
39
|
+
["app", db_role ? nil : "db"]
|
|
40
|
+
end
|
|
41
|
+
end.compact.uniq
|
|
42
|
+
say "server '#{host.vars["ansible_host"]}', user: 'deploy', roles: %w{#{roles.join(' ')}} # #{host.vars["hostname"]}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class Subspace::Commands::
|
|
1
|
+
class Subspace::Commands::Secrets < Subspace::Commands::Base
|
|
2
2
|
def initialize(args, options)
|
|
3
3
|
@environment = args.first
|
|
4
4
|
@action = if options.edit
|
|
@@ -13,13 +13,14 @@ class Subspace::Commands::Vars < Subspace::Commands::Base
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def run
|
|
16
|
+
update_ansible_cfg
|
|
16
17
|
case @action
|
|
17
18
|
when "create"
|
|
18
19
|
create_local
|
|
19
20
|
when "view", "edit"
|
|
20
|
-
ansible_command "ansible-vault", @action, "
|
|
21
|
+
ansible_command "ansible-vault", @action, "secrets/#{@environment}.yml"
|
|
21
22
|
else
|
|
22
|
-
abort "Invalid
|
|
23
|
+
abort "Invalid secrets command"
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -30,7 +31,7 @@ class Subspace::Commands::Vars < Subspace::Commands::Base
|
|
|
30
31
|
end
|
|
31
32
|
src = application_yml_template
|
|
32
33
|
dest = "config/application.yml"
|
|
33
|
-
vars_file = File.join(project_path, "
|
|
34
|
+
vars_file = File.join(project_path, dest_dir, "/secrets/#{@environment}.yml")
|
|
34
35
|
extra_vars = "project_path=#{project_path} vars_file=#{vars_file} src=#{src} dest=#{dest}"
|
|
35
36
|
ansible_command "ansible-playbook", File.join(playbook_dir, "local_template.yml"), "--extra-vars", extra_vars
|
|
36
37
|
say "File created at config/application.yml with #{@environment} secrets"
|
|
@@ -42,7 +43,7 @@ class Subspace::Commands::Vars < Subspace::Commands::Base
|
|
|
42
43
|
private
|
|
43
44
|
|
|
44
45
|
def application_yml_template
|
|
45
|
-
"
|
|
46
|
+
"#{dest_dir}/templates/application.yml.template"
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
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,19 @@ 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
|
-
user =
|
|
21
|
-
host = host_vars["
|
|
22
|
-
port = host_vars["
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
host_vars = inventory.hosts[@host].vars
|
|
21
|
+
user = host_vars["ansible_user"]
|
|
22
|
+
host = host_vars["ansible_host"]
|
|
23
|
+
port = host_vars["ansible_port"] || 22
|
|
24
|
+
pem = host_vars["ansible_ssh_private_key_file"] || 'subspace.pem'
|
|
25
|
+
cmd = "ssh #{user}@#{host} -p #{port} -i config/subspace/#{pem} #{pass_through_params.join(" ")}"
|
|
26
|
+
say "> #{cmd} \n"
|
|
25
27
|
exec cmd
|
|
26
28
|
end
|
|
27
29
|
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 oxenwagen`
|
|
77
|
+
end
|
|
78
|
+
inventory.merge(@output)
|
|
79
|
+
inventory.write
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
module Subspace
|
|
3
|
+
class Inventory
|
|
4
|
+
attr_accessor :group_vars, :hosts, :global_vars, :path
|
|
5
|
+
def initialize
|
|
6
|
+
@hosts = {}
|
|
7
|
+
@group_vars = {}
|
|
8
|
+
@global_vars = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Find all the hosts in the host/group or exit
|
|
12
|
+
def find_hosts!(host_spec)
|
|
13
|
+
if self.groups[host_spec]
|
|
14
|
+
return self.groups[host_spec].host_list.map { |m| self.hosts[m] }
|
|
15
|
+
elsif self.hosts[host_spec]
|
|
16
|
+
return [self.hosts[host_spec]]
|
|
17
|
+
else
|
|
18
|
+
say "No inventory matching: '#{host_spec}' found. "
|
|
19
|
+
say (["Available hosts:"] + self.hosts.keys).join("\n\t")
|
|
20
|
+
say (["Available groups:"] + self.groups.keys).join("\n\t")
|
|
21
|
+
exit
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.read(path)
|
|
26
|
+
inventory = new
|
|
27
|
+
inventory.path = path
|
|
28
|
+
|
|
29
|
+
yml = YAML.load(File.read(path)).to_h
|
|
30
|
+
|
|
31
|
+
# Run through all hosts
|
|
32
|
+
yml["all"]["hosts"].each do |name, vars|
|
|
33
|
+
inventory.hosts[name] = Host.new(name, vars: vars || {})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Run through all children (groups)
|
|
37
|
+
# This does NOT handle sub-groups yet
|
|
38
|
+
yml["all"]["children"].each do |name, group|
|
|
39
|
+
next unless group["hosts"]
|
|
40
|
+
|
|
41
|
+
# Each group defines its host membership
|
|
42
|
+
group["hosts"].each do |host, vars|
|
|
43
|
+
inventory.hosts[host] ||= Host.new(host, vars: vars || {})
|
|
44
|
+
inventory.hosts[host].group_list.push name
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if group["vars"]
|
|
48
|
+
inventory.group_vars[name] = group["vars"]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Capture global variables
|
|
53
|
+
inventory.global_vars = yml["all"]["vars"] || {}
|
|
54
|
+
|
|
55
|
+
inventory
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def write(path=nil)
|
|
59
|
+
File.write(@path || path, self.to_yml)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def merge(inventory_json)
|
|
63
|
+
inventory_json["inventory"]["hostnames"].each_with_index do |host, i|
|
|
64
|
+
if hosts[host]
|
|
65
|
+
old_ip = hosts[host].vars["ansible_host"]
|
|
66
|
+
new_ip = inventory_json["inventory"]["ip_addresses"][i]
|
|
67
|
+
if old_ip != new_ip
|
|
68
|
+
say " * Host '#{host}' IP address changed! You may need to update the inventory! (#{old_ip} => #{new_ip})"
|
|
69
|
+
end
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
hosts[host] = Host.new(host)
|
|
73
|
+
hosts[host].vars["ansible_host"] = inventory_json["inventory"]["ip_addresses"][i]
|
|
74
|
+
hosts[host].vars["ansible_user"] = inventory_json["inventory"]["users"][i]
|
|
75
|
+
hosts[host].vars["hostname"] = host
|
|
76
|
+
hosts[host].group_list = inventory_json["inventory"]["groups"][i].split(/\s/)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_yml
|
|
81
|
+
all_groups = {}
|
|
82
|
+
all_hosts = {}
|
|
83
|
+
@hosts.each do |name, host|
|
|
84
|
+
all_hosts[host.name] = host.vars.empty? ? nil : host.vars.transform_keys(&:to_s)
|
|
85
|
+
host.group_list.each do |group|
|
|
86
|
+
all_groups[group] ||= { "hosts" => {}}
|
|
87
|
+
all_groups[group]["hosts"][host.name] = nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@group_vars.each do |group, vars|
|
|
92
|
+
all_groups[group] ||= {}
|
|
93
|
+
if !vars.empty?
|
|
94
|
+
all_groups[group]["vars"] = vars.transform_keys(&:to_s)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
yml = {
|
|
99
|
+
"all" => {
|
|
100
|
+
"hosts" => all_hosts,
|
|
101
|
+
"children" => all_groups.empty? ? nil : all_groups
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if !@global_vars.empty?
|
|
106
|
+
yml["all"]["vars"] = @global_vars.transform_keys(&:to_s)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
YAML.dump(yml)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def groups
|
|
113
|
+
@groups ||= begin
|
|
114
|
+
all_groups = {"all" => Group.new("all", vars: {}, host_list: hosts.keys) }
|
|
115
|
+
@hosts.each do |name, host|
|
|
116
|
+
host.group_list.each do |group|
|
|
117
|
+
all_groups[group] ||= Group.new(group, vars: @group_vars[group])
|
|
118
|
+
all_groups[group].host_list.append(name)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
all_groups
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class Host
|
|
126
|
+
attr_accessor :vars, :group_list, :name
|
|
127
|
+
|
|
128
|
+
def initialize(name, vars: {}, group_list: [])
|
|
129
|
+
@name = name
|
|
130
|
+
@vars = vars
|
|
131
|
+
@group_list = group_list
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
class Group
|
|
136
|
+
attr_accessor :name, :vars, :host_list
|
|
137
|
+
def initialize(name, vars: {}, host_list: [])
|
|
138
|
+
@name = name
|
|
139
|
+
@vars = vars
|
|
140
|
+
@host_list = host_list
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/subspace/version.rb
CHANGED
data/subspace.gemspec
CHANGED
|
@@ -13,6 +13,13 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.description = %q{WIP -- don't use this :)}
|
|
14
14
|
spec.homepage = "https://github.com/tenforwardconsulting/subspace"
|
|
15
15
|
spec.license = "MIT"
|
|
16
|
+
spec.post_install_message = <<~EOS
|
|
17
|
+
*** Subspace 3 has many breaking changes
|
|
18
|
+
Primarily, the entire configuration directory structure has moved from config/provision to config/subspace.
|
|
19
|
+
You will need to migrate your old configuration to the new location, or downgrade to Subspace 2 if this was not intentional.
|
|
20
|
+
Please review the Upgrade guide: https://github.com/tenforwardconsulting/subspace/UPGRADING.md
|
|
21
|
+
EOS
|
|
22
|
+
|
|
16
23
|
|
|
17
24
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
18
25
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
@@ -24,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
24
31
|
|
|
25
32
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
26
33
|
spec.bindir = "exe"
|
|
27
|
-
spec.executables
|
|
34
|
+
spec.executables << "subspace"
|
|
28
35
|
spec.require_paths = ["lib"]
|
|
29
36
|
|
|
30
37
|
spec.add_development_dependency "bundler", "~> 2.1"
|
|
@@ -33,5 +40,4 @@ Gem::Specification.new do |spec|
|
|
|
33
40
|
|
|
34
41
|
spec.add_runtime_dependency "commander", "~>4.2"
|
|
35
42
|
spec.add_runtime_dependency "figaro", "~>1.0"
|
|
36
|
-
spec.add_runtime_dependency "ed25519", "~>1.0"
|
|
37
43
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[defaults]
|
|
2
|
-
inventory = hosts
|
|
3
2
|
forks = 10
|
|
4
3
|
roles_path = ./roles:<%= File.join(gem_path, 'ansible', 'roles') %>:/etc/ansible/roles
|
|
5
4
|
vault_password_file = .vault_pass
|
|
5
|
+
inventory = inventory.yml
|
|
6
6
|
# Uncomment to add timestamps to tasks to find slow ones.
|
|
7
7
|
# callback_whitelist = profile_tasks
|
|
8
8
|
|
|
@@ -13,4 +13,4 @@ strategy = mitogen_linear
|
|
|
13
13
|
|
|
14
14
|
[ssh_connection]
|
|
15
15
|
pipelining = True
|
|
16
|
-
control_path = /tmp/subspace-control-%%h-%%p-%%r
|
|
16
|
+
control_path = /tmp/subspace-control-%%h-%%p-%%r
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Conventions and defaults are defined here
|
|
2
|
+
project_name: <%= project_name %>
|
|
3
|
+
database_host: localhost
|
|
4
|
+
use_sudo: true
|
|
5
|
+
|
|
6
|
+
# ruby-common
|
|
7
|
+
# pull the checksum/url from https://www.ruby-lang.org/en/downloads/
|
|
8
|
+
ruby_version: # ruby-2.7.1
|
|
9
|
+
ruby_checksum: # d418483bdd0000576c1370571121a6eb24582116db0b7bb2005e90e250eae418
|
|
10
|
+
ruby_download_location: # https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.gz
|
|
11
|
+
bundler_version: # 2.3.5
|
|
12
|
+
|
|
13
|
+
# Other stuff
|
|
14
|
+
letsencrypt_email:
|
|
15
|
+
nodejs_version: # 16.x
|
|
16
|
+
ssl_enabled: true
|
|
17
|
+
postgresql_version: # 14
|
|
18
|
+
|
|
19
|
+
logrotate_scripts:
|
|
20
|
+
- name: rails
|
|
21
|
+
path: "/u/apps/{{project_name}}/shared/log/{{rails_env}}.log"
|
|
22
|
+
options:
|
|
23
|
+
- weekly
|
|
24
|
+
- size 100M
|
|
25
|
+
- missingok
|
|
26
|
+
- compress
|
|
27
|
+
- delaycompress
|
|
28
|
+
- copytruncate
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## rails
|
|
2
|
+
rails_env: <%= @env %>
|
|
3
|
+
|
|
4
|
+
database_pool: 5
|
|
5
|
+
database_name: "{{project_name}}_{{rails_env}}"
|
|
6
|
+
database_user: "{{project_name}}"
|
|
7
|
+
database_adapter: postgresql
|
|
8
|
+
# job_queues:
|
|
9
|
+
# - default
|
|
10
|
+
# - mailers
|
|
11
|
+
|
|
12
|
+
# nginx / letsencrypt
|
|
13
|
+
server_name: "{{ansible_host}}"
|
|
14
|
+
|
|
15
|
+
## postgresql
|
|
16
|
+
# postgresql_version: 14
|
|
17
|
+
# postgresql_authentication:
|
|
18
|
+
# - type: local
|
|
19
|
+
# user: "{{database_user}}"
|
|
20
|
+
# database: 'all'
|
|
21
|
+
# method: trust
|
|
22
|
+
|
|
23
|
+
## puma
|
|
24
|
+
puma_workers: 1
|
|
25
|
+
puma_min_threads: 4
|
|
26
|
+
puma_max_threads: 16
|