taperole 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.tape/ansible.cfg +4 -0
  4. data/CONTRIBUTING.md +22 -0
  5. data/README.md +100 -0
  6. data/Vagrantfile +26 -0
  7. data/ansible.cfg +2 -0
  8. data/bin/tape +81 -0
  9. data/id_rsa_sb_basebox +27 -0
  10. data/lib/tape/ansible_runner.rb +84 -0
  11. data/lib/tape/installer.rb +154 -0
  12. data/lib/tape/qemu_provisioner.rb +167 -0
  13. data/lib/tape/vagrant_provisioner.rb +42 -0
  14. data/lib/tape.rb +76 -0
  15. data/requirements.yml +16 -0
  16. data/roles/after_deploy/tasks/main.yml +1 -0
  17. data/roles/backend_checkout/tasks/main.yml +21 -0
  18. data/roles/backend_config/defaults/main.yml +1 -0
  19. data/roles/backend_config/tasks/main.yml +49 -0
  20. data/roles/backend_config/templates/database.yml.j2 +8 -0
  21. data/roles/backend_config/templates/env_config.yml.j2 +2 -0
  22. data/roles/backend_install_essentials/meta/main.yml +5 -0
  23. data/roles/backend_install_essentials/tasks/main.yml +24 -0
  24. data/roles/backend_install_essentials/templates/memcached.j2 +7 -0
  25. data/roles/database_load/defaults/main.yml +3 -0
  26. data/roles/database_load/meta/main.yml +3 -0
  27. data/roles/database_load/tasks/db_reset.yml +14 -0
  28. data/roles/database_load/tasks/main.yml +21 -0
  29. data/roles/delayed_job/defaults/main.yml +2 -0
  30. data/roles/delayed_job/library/sudo_upstart +101 -0
  31. data/roles/delayed_job/tasks/main.yml +35 -0
  32. data/roles/delayed_job/templates/dj_runner_upstart.j2 +17 -0
  33. data/roles/deployer_user/files/id_rsa_digital_ocean.pub +1 -0
  34. data/roles/deployer_user/tasks/keys.yml +19 -0
  35. data/roles/deployer_user/tasks/main.yml +18 -0
  36. data/roles/frontend_deploy/handlers/main.yml +2 -0
  37. data/roles/frontend_deploy/tasks/main.yml +8 -0
  38. data/roles/general/meta/main.yml +3 -0
  39. data/roles/general/tasks/basic_packages.yml +3 -0
  40. data/roles/general/tasks/main.yml +6 -0
  41. data/roles/general/tasks/swapfile.yml +21 -0
  42. data/roles/monit_activate/tasks/main.yml +2 -0
  43. data/roles/monit_install/tasks/main.yml +19 -0
  44. data/roles/monit_install/templates/web_interface.j2 +2 -0
  45. data/roles/nginx/handlers/main.yml +2 -0
  46. data/roles/nginx/tasks/main.yml +30 -0
  47. data/roles/nginx/templates/nginx_monit.j2 +3 -0
  48. data/roles/nginx/templates/nginx_unicorn.j2 +55 -0
  49. data/roles/postgres/meta/main.yml +15 -0
  50. data/roles/redis/tasks/main.yml +15 -0
  51. data/roles/redis/templates/redis.j2 +10 -0
  52. data/roles/sidekiq/defaults/main.yml +2 -0
  53. data/roles/sidekiq/meta/main.yml +3 -0
  54. data/roles/sidekiq/tasks/main.yml +19 -0
  55. data/roles/sidekiq/templates/sidekiq.j2 +4 -0
  56. data/roles/unicorn_activate/defaults/main.yml +3 -0
  57. data/roles/unicorn_activate/tasks/main.yml +25 -0
  58. data/roles/unicorn_install/tasks/main.yml +24 -0
  59. data/roles/unicorn_install/templates/unicorn.rb.j2 +47 -0
  60. data/roles/unicorn_install/templates/unicorn_init.j2 +70 -0
  61. data/roles/unicorn_install/templates/unicorn_monit.j2 +5 -0
  62. data/taperole.gemspec +11 -0
  63. data/templates/base/deploy.example.yml +17 -0
  64. data/templates/base/hosts.example +7 -0
  65. data/templates/base/omnibox.example.yml +25 -0
  66. data/templates/base/tape_vars.example.yml +13 -0
  67. data/templates/static_html/deploy.example.yml +12 -0
  68. data/templates/static_html/omnibox.example.yml +15 -0
  69. data/templates/static_html/tape_vars.example.yml +7 -0
  70. data/vars/defaults.yml +31 -0
  71. metadata +117 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 62490a5daeb6150d42dc9e9739557f820e0ae934
4
+ data.tar.gz: a93676ee597a4c57cb5cc3fe4097fb0023cb0a37
5
+ SHA512:
6
+ metadata.gz: 097f949627f97b9e2a6e6fd33c0526f7157db46717f46e92fba432f53c920ee2ac9e1de67699fba2b55ef6515b1c6c752b282bf8a0e95e575d710ccd5b8c7aef
7
+ data.tar.gz: 1c90655f1b208db25fdfcd3fbdb6c695e4e0e61967be845d94b527db959c738739872b8e7117cc07541f012273dc7b6d06df3c6ad1ab39dbe23f1c36ec5b1de1
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.img
2
+ .vagrant/
3
+ site_vars.yml
4
+ hosts
5
+ vendor
data/.tape/ansible.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [defaults]
2
+ roles_path=./roles:/Users/bemathis/Dev/Ruby/tape/roles:/Users/bemathis/Dev/Ruby/tape/vendor
3
+ [ssh_connection]
4
+ ssh_args = -o ForwardAgent=yes
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,22 @@
1
+ #Contributor Guidelines
2
+
3
+ ##Submitting a Pull Request
4
+
5
+ ###Workflow
6
+ We use [Waffle.io](https://waffle.io/smashingboxes/tape) to manage our workflow for this project. Please respect the workflow and move PRs that are ready to be mereged into the 'Ready' column.
7
+
8
+ ###PR Structure
9
+
10
+ Please give details regarding your PR in the following format. It really helps with review and quickens the speed at which your changes are merged in
11
+
12
+ ![example pull request]( http://i.imgur.com/HJq8DHc.png )
13
+
14
+ ##Opening Issues
15
+
16
+ ###Basic details
17
+
18
+ Please include the following in the Issues you open
19
+
20
+ * OS Version
21
+ * ansible version
22
+ * tape version
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ [![Stories in Ready](https://badge.waffle.io/smashingboxes/tape.png?label=ready&title=Ready)](https://waffle.io/smashingboxes/tape)
2
+ # Infrastructure Management
3
+
4
+ ## Deploying & provisioning with tape
5
+ **Use Unbuntu precise64 (12.04 x64)**
6
+
7
+ **Enable ssh access via root user**
8
+
9
+ ### Basics
10
+
11
+ **Install**
12
+
13
+ * Add the following to your gemfile.
14
+
15
+ ```ruby
16
+ gem 'tape', github: 'smashingboxes/tape', group: :development, tag: <DESIRED_VERSION>
17
+ ```
18
+
19
+ * `bundle install`
20
+ * `tape installer install`
21
+ * Updated the hosts file with the IP address of your server
22
+
23
+ ```
24
+ [omnibox]
25
+ 0.0.0.0
26
+ ```
27
+
28
+ * Fill in missing values in `tape_vars.yml`
29
+ * Copy all developers public keys into some dir and specify that dir inside `tape_vars.yml` (dev_key_files)
30
+ * `tape ansible everything`
31
+
32
+ **Upgrade**
33
+
34
+ ```
35
+ bundle upgrade tape
36
+ tape installer install
37
+ ```
38
+
39
+ ### Custom roles
40
+ You can write app specific roles in the roles files storred in the `roles` directory
41
+
42
+ You must then specify the roles you want to use in `omnibox.yml` or `deploy.yml`
43
+
44
+ [Read the Ansible docs on playbook roles here](http://docs.ansible.com/playbooks_roles.html)
45
+
46
+ ### Multistage
47
+ You can setup multistage by defining your hosts file as follows
48
+
49
+ ```
50
+ [production]
51
+ 0.0.0.0 be_app_env=SOME_ENV be_app_branch=SOME_BRANCH
52
+ [staging]
53
+ 0.0.0.0 be_app_env=SOME_ENV be_app_branch=SOME_BRANCH
54
+ [omnibox:children]
55
+ production
56
+ staging
57
+ ```
58
+
59
+ then use the `-l` option to specify the staging
60
+
61
+ ```sh
62
+ tape ansible deploy -l staging
63
+ ```
64
+
65
+ ## Testing
66
+ ### With vagrant
67
+
68
+
69
+ 1. `vagrant up` or `tape vagrant create`
70
+ 2. Put the following into your [hosts inventory file](http://docs.ansible.com/intro_inventory.html):
71
+
72
+ ```
73
+ [vagrant]
74
+ <192.168.13.37> ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
75
+ ```
76
+
77
+ The port number might be different if other vagrant machines are running, run `vagrant ssh-config` to find the correct configuration.
78
+ You can speicfy a port using the `ansible_ssh_port` in your hosts inventory file.
79
+
80
+ 3. Update `tape_vars.yml` with information to a [rails app you want to deploy](https://github.com/BrandonMathis/vanilla-rails-app)
81
+ 4. `tape ansible everything -l vagrant`
82
+
83
+
84
+ ### With QEMU
85
+
86
+ 1. `tape qemu create --name fe_test`
87
+ 2. `tape qemu start --name fe_test -p2255`
88
+ 3. `ssh-add ./id_rsa_sb_basebox`
89
+ 4. `echo 'localhost:2255' >test_hosts`
90
+ 5. `tape ansible everything`
91
+
92
+ Run `tape -h` for a quick rundown of the tool's modules and options.
93
+
94
+ ## Development
95
+
96
+ ```sh
97
+ git clone git@github.com:smashingboxes/tape.git
98
+ cd tape
99
+ ansible-galaxy install -r requirements.yml --force
100
+ ```
data/Vagrantfile ADDED
@@ -0,0 +1,26 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant.configure 2 do |config|
5
+ config.vm.box = 'hashicorp/precise64'
6
+
7
+ name = %x[basename `git rev-parse --show-toplevel`].chomp
8
+ config.vm.define "#{name}_vagrant_box"
9
+
10
+ # private_ip = "192.168.13.37"
11
+ # config.vm.network(:private_network, :ip => private_ip)
12
+
13
+ # TODO free me from the bonds of this ip
14
+ config.vm.network 'forwarded_port', guest: 80, host: 8080
15
+ config.vm.network 'private_network', type: 'dhcp'
16
+
17
+ config.ssh.insert_key = false
18
+ config.ssh.shell = 'bash -c "BASH_ENV=/etc/profile exec bash"'
19
+
20
+ config.vm.provision :shell, inline: <<-SCRIPT
21
+ sudo su
22
+ mkdir ~/.ssh/
23
+ cp /home/vagrant/.ssh/authorized_keys ~/.ssh/
24
+ chmod 600 ~/.ssh/authorized_keys
25
+ SCRIPT
26
+ end
data/ansible.cfg ADDED
@@ -0,0 +1,2 @@
1
+ [defaults]
2
+ roles_path = ./vendor
data/bin/tape ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.join(__dir__, '..', 'lib')
4
+
5
+ require 'optparse'
6
+ require 'ostruct'
7
+ require 'tape'
8
+
9
+ options = OpenStruct.new
10
+ opt_parser = OptionParser.new do |opts|
11
+ opts.banner = "Usage: tape <module> <action> [options]"
12
+
13
+ opts.on("-v", "--[no-]verbose", "Be loud") {|v| options.verbose = v}
14
+
15
+ opts.on("-i", "--inventory [INVENTORY_FILE]",
16
+ String, "Do actions with the given inventory file") do |i|
17
+ options.inventory_file = i
18
+ end
19
+
20
+ opts.on('-n', "--name [NAME]",
21
+ String, "The name of the machine to operate on") do |n|
22
+ options.name = n
23
+ end
24
+
25
+ opts.on('-p', "--port [PORT]",
26
+ Integer, "The port that the machine is listening on for SSH connections") do |p|
27
+ options.port = p
28
+ end
29
+
30
+ opts.on("-h", "--help", "Show this help") do
31
+ STDERR.puts opts
32
+ exit 0
33
+ end
34
+
35
+ opts.on("-l", "--limit [PATTERN]",
36
+ String, "Limits ansible runs to hosts matching PATTERN") do |p|
37
+ options.host_pattern = p
38
+ end
39
+
40
+ opts.on("-t", "--tags [TAGS]",
41
+ String, "only run plays and tasks tagged with these values") do |t|
42
+ options.tags = t
43
+ end
44
+
45
+ opts.separator ''
46
+ opts.separator "MODULES"
47
+ TapeBoxer.registered_modules.values.each do |exec_module|
48
+ opts.separator " #{exec_module.name.to_s.upcase}"
49
+ exec_module.klass.actions.values.each do |action|
50
+ opts.separator " #{action.name}: #{action.description}"
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ opt_parser.parse!(ARGV)
57
+
58
+ fail_without = ->(&block){
59
+ val = block.call
60
+
61
+ if val
62
+ return val
63
+ else
64
+ STDERR.puts(opt_parser.help)
65
+ exit 1
66
+ end
67
+ }
68
+
69
+ module_name = fail_without.call{ ARGV.shift }.to_sym
70
+ action_name = fail_without.call{ ARGV.shift }.to_sym
71
+
72
+ exec_module = fail_without.call{TapeBoxer.registered_modules[module_name]}
73
+
74
+ begin
75
+ exec_module.klass.new(options).execute_action(action_name)
76
+ rescue TapeBoxer::InvalidAction,
77
+ TapeBoxer::ActionError, TapeBoxer::UnspecifiedOption => e
78
+
79
+ STDERR.puts(e.message)
80
+ exit 2
81
+ end
data/id_rsa_sb_basebox ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEAzdGlX5Ostx5Tuq8s3ZlImcs3lw5SmDbRi4SCdbnKKeTQJ/M6
3
+ Tjfluj3SVy1ZwzL0gQWLLS9aGBX8/6XRCyddBuBDkkK7Tl+7Xvaz4IuXbl6JOgYr
4
+ zYa4i8SM+mKxUwjfNTgK7df//K4Htzs9dW19CpYvzAnRDE1ghT5RBkLinfuTb3+z
5
+ IjvYWKsovJTiv624OWeRZxWSvIyalfsRKlGBvbjJC5PlAxOzrVbnyEeNh5JAGtiP
6
+ lG/sikqtz0Vd08jXRlwsG/pz65hzWKN8XwTZvKIjXJgdwXyY8VJOjHnsdWOV9JSN
7
+ m5+/nHVpZLBh8u1Ms3As4FngCvsO9Z8dU+f+4QIDAQABAoIBAEjAuJJGYyD/qV0u
8
+ Gs/iJRWoDehpeaywg/WrS2pN2DZi2WmlwpBvldb1j2qdb0Neuar5yK6aNGCbNSkw
9
+ 9enZajrJ/1iuGgOkN1lkH0VaUpcC98L493bZDlbpjWPciw3s7umi+8oDNkudQMD2
10
+ Qc1GfJLHb/HR7oFIwLuYwY9TCUQKTU8KP77HSwKto1MMEBrA+wHd5G6eLKLC+Gvh
11
+ XXkiG2nafs/xgKX6aq4YKcKBJJO7f4tAIeqQZL1x9dJQqhtopj31P8Hv7xNh1zxc
12
+ V61O7gCNM/kP7Mur3XYUFPvLnQOUfPaZCUzxWoJphh5UzBkJyCq4pquA6Q6cx/Im
13
+ NLha5oECgYEA6cCTUCmC7yiKcgUUcw1RFYE91bgxlSo6dliGev9eXbP84N3IReCV
14
+ 8+0TZqTjLKVVANYOBGf3uYC8o0CO6c9Q4t/7bYt0/TuZD/PciCCSNt2GLsr/LVHB
15
+ Qoum6kytm4ZRJDWUPUVv+89xYhXgwogdrZXZc5yR/K0sJUnl+S8St8kCgYEA4Wh4
16
+ Gkp8YKsSCdUaKR46AWRpOh6P4YllcfBj7eeW5+Dp78yLw6bjfxOqNgfMtP3oyKR4
17
+ tic0u0tBIvn3GFFwca+PpYhejdXNivmXGq6bADu+qOQJXtCpOVWhA6rqslJEuAnb
18
+ yAmvwoWEecti4Rqx52z3wV/PWiqfkMTUI7IWSlkCgYBSU7sOnrA430RziAp9wxDt
19
+ zAklPqxn5umUSPWEWHC5++xynmotAj6OHM+adTR1wv0/QCTLtHpxYUAPF2u+OYA5
20
+ h72cBWP81ILCNZdUcnRM0pSx4lkhcwvoDhuJpdv3TMVDRYOiWNRqEdPTQfVZTmt1
21
+ ebOOnnRkU/GaaH6Q1t120QKBgQCF8M0FWmOEEcNbQl0UN9jiAOeiWIzIyRsV1HGc
22
+ 6bO7P+Pi0ZnvfdjLJ0VKa+IRXpuXtlvgaXWlIben7/8LXUFC3L6v71O4VmaYAW3v
23
+ tfd28Ql/VFnZFIaqAh72jfZ+VfJtpbYKrO7a2Pn6YYf1GSbDzPV/283b114P6VXh
24
+ jC5AqQKBgEF8TyZNb5ERgfsbzS9TerP+MbJ4jC7lVrOUZlQeF8URbq81aERIM4Or
25
+ 18RwSXyAywObJWCXYdPdEFjYYaSBw7rSub87/bNiQHZoyTC08R8ZEAIub58HTNav
26
+ taWNCo22rsYcaJysHeIqKhl6iFOYhdOucLFZJZO4Vi/PkGQ9v5V7
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,84 @@
1
+ # Executes ansible commands
2
+ module TapeBoxer
3
+ class AnsibleRunner < ExecutionModule
4
+ TapeBoxer.register_module :ansible, self
5
+
6
+ action :configure_dj_runner,
7
+ proc {ansible '-t configure_dj_runner -e force_dj_runner_restart=true'},
8
+ "Configures and restarts the delayed job runner"
9
+ action :restart_unicorn,
10
+ proc {ansible '-t unicorn_restart'},
11
+ "Restarts the unicorns running on the app servers"
12
+ action :stop_unicorn,
13
+ proc {ansible '-t unicorn_stop -e kill_unicorn=true'},
14
+ "Stops the unicorns running on the app servers"
15
+ action :force_stop_unicorn,
16
+ proc {ansible '-t unicorn_force_stop -e kill_unicorn=true'},
17
+ "Stops the unicorns running on the app servers"
18
+ action :start_unicorn,
19
+ proc {ansible '-t unicorn_start'},
20
+ "Starts the unicorns running on the app servers"
21
+ action :restart_nginx,
22
+ proc {ansible '-t restart_nginx'},
23
+ "Restarts Nginx"
24
+ action :configure_deployer_user,
25
+ proc {ansible '-t deployer'},
26
+ "Ensures the deployer user is present and configures his SSH keys"
27
+ action :reset_db,
28
+ proc {ansible '-t db_reset -e force_db_reset=true'},
29
+ "wipes and re-seeds the DB"
30
+ action :bundle,
31
+ proc {ansible '-t bundle -e force_bundle=true'},
32
+ "Bundles the gems running on the app servers"
33
+ action :fe_deploy,
34
+ proc {ansible_deploy '-t fe_deploy'},
35
+ "Re-deploys fe code"
36
+ action :deploy,
37
+ proc {ansible_deploy '-t be_deploy'},
38
+ "Checks out app code, installs dependencies and restarts unicorns for "\
39
+ "both FE and BE code."
40
+ action :everything, proc {ansible}, "This does it all."
41
+
42
+ def initialize(*args)
43
+ super
44
+ end
45
+
46
+ protected
47
+ attr_reader :opts
48
+
49
+ def ansible(cmd_str = '')
50
+ exec_ansible('omnibox.yml', cmd_str)
51
+ end
52
+
53
+ def ansible_deploy(cmd_str = '')
54
+ exec_ansible('deploy.yml', cmd_str)
55
+ end
56
+
57
+ def exec_ansible(playbook, args)
58
+ enforce_roles_path!
59
+ cmd = "ANSIBLE_CONFIG=#{local_dir}/.tape/ansible.cfg ansible-playbook -i #{inventory_file} #{playbook} #{args} #{hosts_flag} -e tape_dir=#{tape_dir}"
60
+ cmd += ' -vvvv' if opts.verbose
61
+ cmd += " -t #{opts.tags}" if opts.tags
62
+ STDERR.puts "Executing: #{cmd}" if opts.verbose
63
+ Kernel.exec(cmd)
64
+ end
65
+
66
+ def enforce_roles_path!
67
+ Dir.mkdir('.tape') unless Dir.exists?('.tape')
68
+ File.open('.tape/ansible.cfg', 'w') do |f|
69
+ f.puts '[defaults]'
70
+ f.puts "roles_path=./roles:#{tape_dir}/roles:#{tape_dir}/vendor"
71
+ f.puts '[ssh_connection]'
72
+ f.puts 'ssh_args = -o ForwardAgent=yes'
73
+ end
74
+ end
75
+
76
+ def hosts_flag
77
+ "-l #{opts.host_pattern}" if opts.host_pattern
78
+ end
79
+
80
+ def inventory_file
81
+ opts.inventory_file || "#{local_dir}/hosts"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,154 @@
1
+ module TapeBoxer
2
+ class Installer < ExecutionModule
3
+ TapeBoxer.register_module :installer, self
4
+
5
+ action :dependencies,
6
+ proc { dependencies },
7
+ 'Install dependencies'
8
+ action :install,
9
+ proc {install},
10
+ 'Creates all nessisary hosts and config files'
11
+ action :uninstall,
12
+ proc {uninstall},
13
+ 'Cleans up files generated by the installer'
14
+
15
+ def initialize(*args)
16
+ super
17
+ end
18
+
19
+ protected
20
+
21
+ def dependencies
22
+ puts 'Dependencies:'
23
+
24
+ if system "ansible-galaxy install -r #{tape_dir}/requirements.yml -p #{tape_dir}/vendor --force"
25
+ print 'Installing/updating dependencies: '
26
+ puts '✔'.green
27
+ else
28
+ puts '✘'.red
29
+ end
30
+ end
31
+
32
+ def install
33
+ dependencies
34
+ File.open('.gitignore', 'r+') { |f| f.puts '.tape' unless f.read =~/^\.tape$/ }
35
+ mkdir 'roles'
36
+ if fe_app? && !rails_app?
37
+ puts '🔎 JS/HTML app detected'.pink
38
+ copy_static_app_examples
39
+ else rails_app?
40
+ puts '🔎 Rails app detected'.pink
41
+ copy_basic_examples
42
+ end
43
+ copy_example 'templates/base/hosts.example', 'hosts'
44
+ mkdir 'dev_keys'
45
+ print 'Are you going to use vagrant? (y/n): '
46
+ if gets.chomp == 'y'
47
+ copy_example 'Vagrantfile', 'Vagrantfile'
48
+ end
49
+ end
50
+
51
+ def fe_app?
52
+ !Dir["#{local_dir}/gulpfile.*"].empty?
53
+ end
54
+
55
+ def rails_app?
56
+ !Dir["#{local_dir}/config.ru"].empty?
57
+ end
58
+
59
+ def copy_static_app_examples
60
+ copy_example 'templates/static_html/omnibox.example.yml', 'omnibox.yml'
61
+ copy_example 'templates/static_html/deploy.example.yml', 'deploy.yml'
62
+ copy_example 'templates/static_html/tape_vars.example.yml', 'tape_vars.yml'
63
+ end
64
+
65
+ def copy_basic_examples
66
+ copy_example 'templates/base/omnibox.example.yml', 'omnibox.yml'
67
+ copy_example 'templates/base/deploy.example.yml', 'deploy.yml'
68
+ copy_example 'templates/base/tape_vars.example.yml', 'tape_vars.yml'
69
+ end
70
+
71
+ def uninstall
72
+ rm 'omnibox.yml'
73
+ rm 'deploy.yml'
74
+ rm 'tape_vars.yml'
75
+ rm 'roles'
76
+ rm 'hosts'
77
+ rm 'dev_keys'
78
+ rm 'Vagrantfile'
79
+ end
80
+
81
+ def rm(file)
82
+ print 'Deleting '.red
83
+ FileUtils.rm_r "#{local_dir}/#{file}"
84
+ puts file
85
+ end
86
+
87
+ def mkdir(name)
88
+ print "#{name}: "
89
+ begin
90
+ FileUtils.mkdir name
91
+ success
92
+ rescue Errno::EEXIST
93
+ exists
94
+ rescue Exception => e
95
+ error
96
+ raise e
97
+ end
98
+ end
99
+
100
+ def touch(file)
101
+ File.new "#{local_dir}/#{file}", 'w'
102
+ end
103
+
104
+ def copy_example(file, cp_file)
105
+ print "#{cp_file}: "
106
+ begin
107
+ if File.exists?("#{local_dir}/#{cp_file}")
108
+ exists
109
+ else
110
+ FileUtils.cp("#{tape_dir}/#{file}", "#{local_dir}/#{cp_file}")
111
+ success
112
+ end
113
+ rescue Exception => e
114
+ error
115
+ raise e
116
+ end
117
+ end
118
+
119
+ def success
120
+ puts '✔'.green
121
+ end
122
+
123
+ def error
124
+ puts '✘'.red
125
+ end
126
+
127
+ def exists
128
+ puts '✘ (Exists)'.yellow
129
+ end
130
+ end
131
+ end
132
+
133
+ class String
134
+ # colorization
135
+ def colorize(color_code)
136
+ "\e[#{color_code}m#{self}\e[0m"
137
+ end
138
+
139
+ def red
140
+ colorize(31)
141
+ end
142
+
143
+ def green
144
+ colorize(32)
145
+ end
146
+
147
+ def yellow
148
+ colorize(33)
149
+ end
150
+
151
+ def pink
152
+ colorize(35)
153
+ end
154
+ end
@@ -0,0 +1,167 @@
1
+ require 'fileutils'
2
+ module TapeBoxer
3
+ class QemuProvisioner < ExecutionModule
4
+ BASE_IMG = File.realpath(File.join(__dir__, ''))
5
+ HOSTED_IMG_PATH = 'http://d.pr/f/17cOG/434tIaDx+'
6
+ PIDFILE_DIR = "/tmp/tape"
7
+ BOXIMG_DIR = File.join(ENV['HOME'], '.tape', 'boxes')
8
+ BOXLOG_DIR = '/tmp/tape'
9
+ DEFAULT_PORT = 2255
10
+
11
+ TapeBoxer.register_module :qemu, self
12
+
13
+ action :create,
14
+ proc {create_img},
15
+ 'Creates a new qemu box with the given name'
16
+ action :start,
17
+ proc {start_box},
18
+ 'Ensures the qemu box by the given name is running'
19
+ action :ssh,
20
+ proc {ssh_to_box},
21
+ 'SSHes to the box by the given name. Requires id_rsa_sb_basebox
22
+ key to be added to the current SSH agent'
23
+ action :reset,
24
+ proc {reset_box},
25
+ 'Destroys and re-creates the box by the given name'
26
+
27
+ protected
28
+
29
+ def ssh_to_box
30
+ require_opt :name
31
+ ensure_box_running
32
+ Kernel.exec("ssh root@127.0.0.1 -p #{port}")
33
+ end
34
+
35
+ def create_img
36
+ require_opt :name
37
+ img_name = opts[:name] + '.img'
38
+
39
+ if File.exists?(image_path)
40
+ STDERR.puts "That image already exists!"
41
+ exit 1
42
+ else
43
+ Kernel.exec "qemu-img create -b '#{base_image_path}' -fqcow2 '#{image_path}'"
44
+ end
45
+ end
46
+
47
+ def start_box
48
+ require_opt :name
49
+ ensure_box_created
50
+ ensure_box_not_running
51
+
52
+ cmd = "qemu-system-x86_64 -m 512 "\
53
+ "-netdev user,hostfwd=tcp:127.0.0.1:#{qemu_port}-:22,id=net.0 "\
54
+ "-device e1000,netdev=net.0 "\
55
+ "-nographic -enable-kvm #{image_path}"
56
+
57
+ pid = Process.spawn(cmd, out: boxlog_path)
58
+
59
+ write_pidfile(pid)
60
+ Process.detach(pid)
61
+ end
62
+
63
+ def qemu_port
64
+ opts.port || DEFAULT_PORT
65
+ end
66
+
67
+ def reset_box
68
+ require_opt :name
69
+ ensure_box_created
70
+
71
+ File.delete(image_path)
72
+ create_img
73
+ end
74
+
75
+ def ensure_box_created
76
+ unless File.exists?(image_path)
77
+ raise ActionError, "The machine (#{opts.name}) has not yet been created!"
78
+ end
79
+ end
80
+
81
+ def ensure_box_running
82
+ unless pidfile_present? and process_alive?(read_pidfile)
83
+ raise ActionError, "The machine(#{opts.name}) needs to be "\
84
+ "running before you can do this!"
85
+ end
86
+ end
87
+
88
+ def port
89
+ opts.port or 2255
90
+ end
91
+
92
+ def ensure_box_not_running
93
+ if pidfile_present? and process_alive?(read_pidfile)
94
+ raise ActionError, "The machine (#{opts.name}) is already running!"
95
+ end
96
+ end
97
+
98
+ def process_alive?(pid)
99
+ Process.kill(0, pid)
100
+ rescue Errno::ESRCH
101
+ return false
102
+ end
103
+
104
+ def base_image_path
105
+ img = "sb_ubuntu_12.04_x64_base.img"
106
+ path = File.join(image_dir, img)
107
+ unless File.exists?(path)
108
+ print 'base image #{path} not found. Dow you want to download (y/n) '
109
+ if STDOUT.flush and gets.chomp.downcase == 'y'
110
+ %x[curl -0L #{HOSTED_IMG_PATH} > #{image_dir}/#{img}]
111
+ path = File.join(image_dir, img)
112
+ end
113
+ end
114
+
115
+ return path
116
+ end
117
+
118
+ def pidfile_present?
119
+ File.exists? pidfile_path
120
+ end
121
+
122
+ def image_path
123
+ File.expand_path(File.join(image_dir, "qemu-#{opts.name}.img"))
124
+ end
125
+
126
+ def delete_pidfile
127
+ File.delete(pidfile_path)
128
+ end
129
+
130
+ def write_pidfile(pid)
131
+ File.open(pidfile_path, 'w').write(pid.to_s)
132
+ end
133
+
134
+ def read_pidfile
135
+ STDERR.puts "Reading pidfile #{pidfile_path}" if opts.verbose
136
+ pid = File.open(pidfile_path, 'r').read.to_i
137
+
138
+ unless pid > 0
139
+ raise ActionError, "Pidfile (#{pidfile_path}) does not appear to be numeric!"
140
+ end
141
+
142
+ return pid
143
+ end
144
+
145
+ def boxlog_path
146
+ @boxlog_path ||=
147
+ File.expand_path(File.join(boxlog_dir, "qemu-#{opts.name}.log"))
148
+ end
149
+
150
+ def pidfile_path
151
+ @pidfile_path ||=
152
+ File.expand_path(File.join(pidfile_dir, "qemu-#{opts[:name]}.pid"))
153
+ end
154
+
155
+ def boxlog_dir
156
+ FileUtils.mkdir_p(PIDFILE_DIR) && PIDFILE_DIR
157
+ end
158
+
159
+ def pidfile_dir
160
+ FileUtils.mkdir_p(PIDFILE_DIR) && PIDFILE_DIR
161
+ end
162
+
163
+ def image_dir
164
+ FileUtils.mkdir_p(BOXIMG_DIR) && BOXIMG_DIR
165
+ end
166
+ end
167
+ end