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