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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +12 -5
  4. data/README.md +57 -24
  5. data/UPGRADING.md +10 -0
  6. data/ansible/roles/common/defaults/main.yml +0 -1
  7. data/ansible/roles/common/files/sudoers-service +1 -1
  8. data/ansible/roles/common/tasks/main.yml +18 -7
  9. data/ansible/roles/common/tasks/no_swap.yml +26 -0
  10. data/ansible/roles/common/templates/motd +1 -1
  11. data/ansible/roles/common/templates/motd2 +1 -1
  12. data/ansible/roles/delayed_job/tasks/main.yml +1 -1
  13. data/ansible/roles/memcache/defaults/main.yml +2 -0
  14. data/ansible/roles/memcache/tasks/main.yml +16 -1
  15. data/ansible/roles/newrelic-infra/tasks/main.yml +3 -3
  16. data/ansible/roles/nginx/tasks/main.yml +12 -3
  17. data/ansible/roles/puma/tasks/main.yml +32 -20
  18. data/ansible/roles/puma/templates/puma-systemd.service +36 -0
  19. data/ansible/roles/puma/templates/puma-systemd.socket +14 -0
  20. data/ansible/roles/puma/templates/puma.rb +4 -2
  21. data/ansible/roles/rails/defaults/main.yml +0 -7
  22. data/ansible/roles/redis/tasks/main.yml +7 -0
  23. data/ansible/roles/resque/tasks/main.yml +11 -12
  24. data/ansible/roles/resque/templates/resque-systemd.service +10 -3
  25. data/ansible/roles/ruby-common/README.md +1 -1
  26. data/ansible/roles/ruby-common/tasks/main.yml +2 -17
  27. data/ansible/roles/sidekiq/defaults/main.yml +1 -1
  28. data/ansible/roles/sidekiq/tasks/main.yml +11 -15
  29. data/ansible/roles/sidekiq/templates/sidekiq-monit-rc +1 -1
  30. data/ansible/roles/sidekiq/templates/sidekiq-systemd.service +62 -0
  31. data/ansible/roles/tailscale/defaults/main.yml +2 -0
  32. data/ansible/roles/tailscale/tasks/main.yml +22 -0
  33. data/exe/subspace +1 -2
  34. data/lib/subspace/cli.rb +50 -14
  35. data/lib/subspace/commands/ansible.rb +11 -2
  36. data/lib/subspace/commands/base.rb +20 -5
  37. data/lib/subspace/commands/bootstrap.rb +16 -21
  38. data/lib/subspace/commands/configure.rb +2 -2
  39. data/lib/subspace/commands/exec.rb +20 -0
  40. data/lib/subspace/commands/init.rb +94 -45
  41. data/lib/subspace/commands/inventory.rb +45 -0
  42. data/lib/subspace/commands/maintain.rb +1 -1
  43. data/lib/subspace/commands/provision.rb +1 -3
  44. data/lib/subspace/commands/{vars.rb → secrets.rb} +6 -5
  45. data/lib/subspace/commands/ssh.rb +10 -8
  46. data/lib/subspace/commands/terraform.rb +83 -0
  47. data/lib/subspace/inventory.rb +144 -0
  48. data/lib/subspace/version.rb +1 -1
  49. data/subspace.gemspec +8 -2
  50. data/template/{provision → subspace}/.gitignore +3 -0
  51. data/template/{provision → subspace}/ansible.cfg.erb +2 -2
  52. data/template/subspace/group_vars/all.erb +28 -0
  53. data/template/subspace/group_vars/template.erb +26 -0
  54. data/template/subspace/inventory.yml.erb +11 -0
  55. data/template/{provision → subspace}/playbook.yml.erb +2 -5
  56. data/template/subspace/templates/authorized_keys.erb +1 -0
  57. data/template/subspace/terraform/.gitignore +2 -0
  58. data/template/subspace/terraform/template/main-oxenwagen.tf.erb +116 -0
  59. data/template/subspace/terraform/template/main-workhorse.tf.erb +41 -0
  60. data/template/subspace/terraformrc.erb +9 -0
  61. data/terraform/modules/s3_backend/README +2 -0
  62. data/terraform/modules/s3_backend/dynamodb.tf +1 -0
  63. data/terraform/modules/s3_backend/iam_user.tf +38 -0
  64. data/terraform/modules/s3_backend/main.tf +39 -0
  65. data/terraform/modules/s3_backend/state_bucket.tf +14 -0
  66. metadata +42 -53
  67. data/ansible/roles/monit/files/monit-http.conf +0 -3
  68. data/ansible/roles/monit/files/sudoers-monit +0 -1
  69. data/ansible/roles/monit/handlers/main.yml +0 -14
  70. data/ansible/roles/monit/tasks/main.yml +0 -34
  71. data/ansible/roles/mtpereira.passenger/.bumpversion.cfg +0 -7
  72. data/ansible/roles/mtpereira.passenger/.gitignore +0 -2
  73. data/ansible/roles/mtpereira.passenger/LICENSE +0 -20
  74. data/ansible/roles/mtpereira.passenger/README.md +0 -31
  75. data/ansible/roles/mtpereira.passenger/defaults/main.yml +0 -5
  76. data/ansible/roles/mtpereira.passenger/handlers/main.yml +0 -8
  77. data/ansible/roles/mtpereira.passenger/meta/.galaxy_install_info +0 -1
  78. data/ansible/roles/mtpereira.passenger/meta/main.yml +0 -21
  79. data/ansible/roles/mtpereira.passenger/tasks/apt.yml +0 -13
  80. data/ansible/roles/mtpereira.passenger/tasks/main.yml +0 -8
  81. data/ansible/roles/mtpereira.passenger/tasks/pkg.yml +0 -35
  82. data/ansible/roles/mtpereira.passenger/tasks/service.yml +0 -8
  83. data/ansible/roles/passenger/files/sudoers-passenger +0 -1
  84. data/ansible/roles/passenger/meta/main.yml +0 -6
  85. data/ansible/roles/passenger/tasks/main.yml +0 -5
  86. data/ansible/roles/postgis/defaults/main.yml +0 -2
  87. data/ansible/roles/puma/defaults/main.yml +0 -5
  88. data/ansible/roles/puma/meta/main.yml +0 -5
  89. data/ansible/roles/sidekiq/meta/main.yml +0 -5
  90. data/template/provision/group_vars/all.erb +0 -17
  91. data/template/provision/group_vars/template.erb +0 -11
  92. data/template/provision/host_vars/template.erb +0 -4
  93. /data/template/{provision → subspace}/hosts.erb +0 -0
  94. /data/template/{provision/vars → subspace/secrets}/template.erb +0 -0
  95. /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
- if args.first == "vars"
7
- init_vars
8
- else
9
- run
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
- FileUtils.mkdir_p File.join dest_dir, "group_vars"
20
- FileUtils.mkdir_p File.join dest_dir, "host_vars"
21
- FileUtils.mkdir_p File.join dest_dir, "vars"
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
- copy ".gitignore"
25
- #template "../provision.rb"
26
- template "ansible.cfg"
27
- template "hosts"
28
- template "group_vars/all"
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
- create_vault_pass
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
- puts """
43
- 1. Create a server.
50
+ subspace tf #{@env}
44
51
 
45
- 2. Set your server's location:
52
+ 3. Bootstrap the new server
46
53
 
47
- vim config/provision/host_vars/production
48
- vim config/provision/host_vars/dev
54
+ subspace boostrap #{@env}1
49
55
 
50
- 3. Set up your authorized_keys:
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
- vim config/provision/authorized_keys
60
+ 4. Provision the new servers with ansible:
53
61
 
54
- 4. Then provision your server:
62
+ subspace provision #{@env}
55
63
 
56
- subspace provision dev
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
- def init_vars
64
- FileUtils.mkdir_p File.join dest_dir, "templates"
65
- copy "templates/application.yml.template"
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
- private
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
- def project_name
71
- File.basename(Dir.pwd)
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 environments
75
- %w(production dev)
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 create_vars_file_for_env(env)
91
- template "vars/template", "vars/#{env}.yml"
127
+ def create_secrets_for(env)
128
+ template "secrets/template", "secrets/#{env}.yml"
92
129
  Dir.chdir dest_dir do
93
- `ansible-vault encrypt vars/#{env}.yml`
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
- ansible_command "ansible-playbook", "#{@environment}.yml", *ansible_options
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
- ansible_options = ["--diff"]
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::Vars < Subspace::Commands::Base
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, "vars/#{@environment}.yml"
21
+ ansible_command "ansible-vault", @action, "secrets/#{@environment}.yml"
21
22
  else
22
- abort "Invalid vars command"
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, "config/provision/vars/#{@environment}.yml")
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
- "config/provision/templates/application.yml.template"
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 !File.exists? "config/provision/host_vars/#{@host}"
14
+ if !inventory.hosts[@host]
14
15
  say "No host '#{@host}' found. "
15
- all_hosts = Dir["config/provision/host_vars/*"].collect {|f| File.basename(f) }
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 = YAML.load_file("config/provision/host_vars/#{@host}")
20
- user = @user || host_vars["ansible_ssh_user"] || host_vars["ansible_user"]
21
- host = host_vars["ansible_ssh_host"] || host_vars["ansible_host"]
22
- port = host_vars["ansible_ssh_port"] || host_vars["ansible_port"] || 22
23
- cmd = "ssh #{user}@#{host} -p #{port} #{pass_through_params.join(" ")}"
24
- say cmd
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
@@ -1,3 +1,3 @@
1
1
  module Subspace
2
- VERSION = "2.5.10"
2
+ VERSION = "3.0.0.rc1"
3
3
  end
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 = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
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,3 +1,6 @@
1
1
  .vault_pass
2
2
  ansible.cfg
3
3
  *.retry
4
+ *.tfstate*
5
+ *.pem
6
+ .terraform
@@ -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
@@ -0,0 +1,11 @@
1
+ ---
2
+ all:
3
+ hosts:
4
+ <%= @env %>1:
5
+ ansible_host: localhost
6
+ ansible_user: ubuntu
7
+ ansible_ssh_private_key_file: subspace.pem
8
+ hostname: web1
9
+ children:
10
+ <%= @env %>:
11
+ <%= @env %>1: