subspace 2.5.10 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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