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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +22 -5
  4. data/README.md +105 -51
  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 +21 -38
  13. data/ansible/roles/delayed_job/templates/delayed-job-systemd.service +33 -0
  14. data/ansible/roles/letsencrypt/defaults/main.yml +7 -7
  15. data/ansible/roles/letsencrypt/tasks/main.yml +18 -24
  16. data/ansible/roles/memcache/defaults/main.yml +2 -0
  17. data/ansible/roles/memcache/tasks/main.yml +16 -1
  18. data/ansible/roles/newrelic-infra/tasks/main.yml +3 -3
  19. data/ansible/roles/nginx/tasks/main.yml +12 -3
  20. data/ansible/roles/puma/tasks/main.yml +32 -20
  21. data/ansible/roles/puma/templates/puma-systemd.service +37 -0
  22. data/ansible/roles/puma/templates/puma-systemd.socket +14 -0
  23. data/ansible/roles/puma/templates/puma.rb +4 -2
  24. data/ansible/roles/rails/defaults/main.yml +0 -7
  25. data/ansible/roles/redis/tasks/main.yml +28 -3
  26. data/ansible/roles/resque/tasks/main.yml +11 -12
  27. data/ansible/roles/resque/templates/resque-systemd.service +10 -3
  28. data/ansible/roles/ruby-common/tasks/main.yml +1 -16
  29. data/ansible/roles/sidekiq/defaults/main.yml +1 -1
  30. data/ansible/roles/sidekiq/tasks/main.yml +11 -15
  31. data/ansible/roles/sidekiq/templates/sidekiq-monit-rc +1 -1
  32. data/ansible/roles/sidekiq/templates/sidekiq-systemd.service +63 -0
  33. data/ansible/roles/tailscale/defaults/main.yml +2 -0
  34. data/ansible/roles/tailscale/tasks/main.yml +22 -0
  35. data/bin/console +0 -4
  36. data/exe/subspace +1 -2
  37. data/lib/subspace/cli.rb +51 -14
  38. data/lib/subspace/commands/ansible.rb +12 -3
  39. data/lib/subspace/commands/base.rb +20 -5
  40. data/lib/subspace/commands/bootstrap.rb +16 -21
  41. data/lib/subspace/commands/configure.rb +2 -2
  42. data/lib/subspace/commands/exec.rb +20 -0
  43. data/lib/subspace/commands/init.rb +94 -45
  44. data/lib/subspace/commands/inventory.rb +54 -0
  45. data/lib/subspace/commands/maintain.rb +1 -1
  46. data/lib/subspace/commands/provision.rb +1 -3
  47. data/lib/subspace/commands/secrets.rb +69 -0
  48. data/lib/subspace/commands/ssh.rb +14 -8
  49. data/lib/subspace/commands/terraform.rb +83 -0
  50. data/lib/subspace/inventory.rb +144 -0
  51. data/lib/subspace/version.rb +1 -1
  52. data/subspace.gemspec +8 -2
  53. data/template/{provision → subspace}/.gitignore +3 -0
  54. data/template/{provision → subspace}/ansible.cfg.erb +2 -2
  55. data/template/subspace/group_vars/all.erb +28 -0
  56. data/template/subspace/group_vars/template.erb +26 -0
  57. data/template/{provision → subspace}/hosts.erb +0 -0
  58. data/template/subspace/inventory.yml.erb +11 -0
  59. data/template/{provision → subspace}/playbook.yml.erb +2 -5
  60. data/template/{provision/vars → subspace/secrets}/template.erb +0 -0
  61. data/template/{provision → subspace}/templates/application.yml.template +0 -0
  62. data/template/subspace/templates/authorized_keys.erb +1 -0
  63. data/template/subspace/terraform/.gitignore +2 -0
  64. data/template/subspace/terraform/template/main-oxenwagen.tf.erb +116 -0
  65. data/template/subspace/terraform/template/main-workhorse.tf.erb +41 -0
  66. data/template/subspace/terraformrc.erb +9 -0
  67. data/terraform/modules/s3_backend/README +2 -0
  68. data/terraform/modules/s3_backend/dynamodb.tf +1 -0
  69. data/terraform/modules/s3_backend/iam_user.tf +38 -0
  70. data/terraform/modules/s3_backend/main.tf +39 -0
  71. data/terraform/modules/s3_backend/state_bucket.tf +14 -0
  72. metadata +41 -55
  73. data/ansible/roles/awscli/tasks/main.yml +0 -10
  74. data/ansible/roles/delayed_job/meta/main.yml +0 -5
  75. data/ansible/roles/letsencrypt_dns/defaults/main.yml +0 -4
  76. data/ansible/roles/letsencrypt_dns/tasks/main.yml +0 -133
  77. data/ansible/roles/monit/files/monit-http.conf +0 -3
  78. data/ansible/roles/monit/files/sudoers-monit +0 -1
  79. data/ansible/roles/monit/handlers/main.yml +0 -14
  80. data/ansible/roles/monit/tasks/main.yml +0 -34
  81. data/ansible/roles/mtpereira.passenger/.bumpversion.cfg +0 -7
  82. data/ansible/roles/mtpereira.passenger/.gitignore +0 -2
  83. data/ansible/roles/mtpereira.passenger/LICENSE +0 -20
  84. data/ansible/roles/mtpereira.passenger/README.md +0 -31
  85. data/ansible/roles/mtpereira.passenger/defaults/main.yml +0 -5
  86. data/ansible/roles/mtpereira.passenger/handlers/main.yml +0 -8
  87. data/ansible/roles/mtpereira.passenger/meta/.galaxy_install_info +0 -1
  88. data/ansible/roles/mtpereira.passenger/meta/main.yml +0 -21
  89. data/ansible/roles/mtpereira.passenger/tasks/apt.yml +0 -13
  90. data/ansible/roles/mtpereira.passenger/tasks/main.yml +0 -8
  91. data/ansible/roles/mtpereira.passenger/tasks/pkg.yml +0 -35
  92. data/ansible/roles/mtpereira.passenger/tasks/service.yml +0 -8
  93. data/ansible/roles/passenger/files/sudoers-passenger +0 -1
  94. data/ansible/roles/passenger/meta/main.yml +0 -6
  95. data/ansible/roles/passenger/tasks/main.yml +0 -5
  96. data/ansible/roles/postgis/defaults/main.yml +0 -2
  97. data/ansible/roles/puma/defaults/main.yml +0 -5
  98. data/ansible/roles/puma/meta/main.yml +0 -5
  99. data/ansible/roles/sidekiq/meta/main.yml +0 -5
  100. data/lib/subspace/commands/vars.rb +0 -48
  101. data/template/provision/group_vars/all.erb +0 -17
  102. data/template/provision/group_vars/template.erb +0 -11
  103. 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
- Dir.chdir "config/provision" do
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 !ENV["DISABLE_MITOGEN"] && `pip show mitogen 2>&1` =~ /^Location: (.*?)$/m
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', 'provision')
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/provision"
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")), nil, '-'
40
- File.write File.join(dest_dir, dest), template.result(render_binding || binding)
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
- install_python
15
- ensure_ssh_dir
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 ensure_ssh_dir
21
- cmd = ["ansible",
22
- @host_spec,
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
- @host_spec,
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/python || (apt -y update && apt install -y python-minimal)",
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/provisiong/host_vars/#{host}"
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/provisiong/group_vars/#{group}"
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
- 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,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
- 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
@@ -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 !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
+ 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