Skip to content

Commit fc72d7c

Browse files
committedAug 12, 2013
Initial
0 parents  commit fc72d7c

32 files changed

+1070
-0
lines changed
 

‎.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.*.swp
2+
log
3+
/config/01-development.yaml
4+
/config/01-production.yaml
5+
Capfile
6+
dump.rdb
7+
.vagrant

‎Gemfile

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
source :rubygems
2+
gem 'sinatra', '>= 1.0'
3+
gem 'dm-sqlite-adapter'
4+
gem 'rake'
5+
gem 'haml'
6+
gem 'json'
7+
gem 'bson_ext'
8+
gem 'rbvmomi', :git => 'https://github.com/giuliano108/rbvmomi.git'
9+
gem 'rb-readline'
10+
gem 'ffi'
11+
gem 'omniauth'
12+
gem 'omniauth-openid'
13+
gem 'sinatra-flash'
14+
gem 'pony'
15+
gem 'data_mapper'
16+
gem 'resque'
17+
gem 'foreman'
18+
gem 'thin'
19+
gem 'resque-status'
20+
gem 'activemodel'
21+
gem 'nsconfig', :git => 'git://github.com/giuliano108/nsconfig.git', :tag => 'v0.1.0'
22+
23+
group :production do
24+
gem 'dm-mysql-adapter'
25+
end
26+
27+
group :development do
28+
gem 'sinatra-reloader'
29+
gem 'pry'
30+
gem 'dm-sqlite-adapter'
31+
end

‎Gemfile.lock

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
GIT
2+
remote: git://github.com/giuliano108/nsconfig.git
3+
revision: f49d7b7d7a67152a6572c017f79d81b33e6c95ad
4+
tag: v0.1.0
5+
specs:
6+
nsconfig (0.1.0)
7+
8+
GIT
9+
remote: https://github.com/giuliano108/rbvmomi.git
10+
revision: 2ee183569bda8798e95a64c725fcd7c7f03aaa15
11+
specs:
12+
rbvmomi (1.6.0)
13+
builder
14+
nokogiri (>= 1.4.1)
15+
trollop
16+
17+
GEM
18+
remote: http://rubygems.org/
19+
specs:
20+
activemodel (3.2.8)
21+
activesupport (= 3.2.8)
22+
builder (~> 3.0.0)
23+
activesupport (3.2.8)
24+
i18n (~> 0.6)
25+
multi_json (~> 1.0)
26+
addressable (2.2.8)
27+
backports (2.6.5)
28+
bcrypt-ruby (3.0.1)
29+
bson (1.7.0)
30+
bson_ext (1.7.0)
31+
bson (~> 1.7.0)
32+
builder (3.0.4)
33+
coderay (1.0.9)
34+
daemons (1.1.9)
35+
data_mapper (1.2.0)
36+
dm-aggregates (~> 1.2.0)
37+
dm-constraints (~> 1.2.0)
38+
dm-core (~> 1.2.0)
39+
dm-migrations (~> 1.2.0)
40+
dm-serializer (~> 1.2.0)
41+
dm-timestamps (~> 1.2.0)
42+
dm-transactions (~> 1.2.0)
43+
dm-types (~> 1.2.0)
44+
dm-validations (~> 1.2.0)
45+
data_objects (0.10.10)
46+
addressable (~> 2.1)
47+
dm-aggregates (1.2.0)
48+
dm-core (~> 1.2.0)
49+
dm-constraints (1.2.0)
50+
dm-core (~> 1.2.0)
51+
dm-core (1.2.0)
52+
addressable (~> 2.2.6)
53+
dm-do-adapter (1.2.0)
54+
data_objects (~> 0.10.6)
55+
dm-core (~> 1.2.0)
56+
dm-migrations (1.2.0)
57+
dm-core (~> 1.2.0)
58+
dm-mysql-adapter (1.2.0)
59+
dm-do-adapter (~> 1.2.0)
60+
do_mysql (~> 0.10.6)
61+
dm-serializer (1.2.2)
62+
dm-core (~> 1.2.0)
63+
fastercsv (~> 1.5)
64+
json (~> 1.6)
65+
json_pure (~> 1.6)
66+
multi_json (~> 1.0)
67+
dm-sqlite-adapter (1.2.0)
68+
dm-do-adapter (~> 1.2.0)
69+
do_sqlite3 (~> 0.10.6)
70+
dm-timestamps (1.2.0)
71+
dm-core (~> 1.2.0)
72+
dm-transactions (1.2.0)
73+
dm-core (~> 1.2.0)
74+
dm-types (1.2.2)
75+
bcrypt-ruby (~> 3.0)
76+
dm-core (~> 1.2.0)
77+
fastercsv (~> 1.5)
78+
json (~> 1.6)
79+
multi_json (~> 1.0)
80+
stringex (~> 1.4)
81+
uuidtools (~> 2.1)
82+
dm-validations (1.2.0)
83+
dm-core (~> 1.2.0)
84+
do_mysql (0.10.10)
85+
data_objects (= 0.10.10)
86+
do_sqlite3 (0.10.10)
87+
data_objects (= 0.10.10)
88+
eventmachine (1.0.0)
89+
fastercsv (1.5.5)
90+
ffi (1.1.5)
91+
foreman (0.60.2)
92+
thor (>= 0.13.6)
93+
haml (3.1.7)
94+
hashie (1.2.0)
95+
i18n (0.6.1)
96+
json (1.7.5)
97+
json_pure (1.7.5)
98+
mail (2.4.4)
99+
i18n (>= 0.4.0)
100+
mime-types (~> 1.16)
101+
treetop (~> 1.4.8)
102+
method_source (0.8.2)
103+
mime-types (1.19)
104+
multi_json (1.3.7)
105+
nokogiri (1.5.5)
106+
omniauth (1.1.1)
107+
hashie (~> 1.2)
108+
rack
109+
omniauth-openid (1.0.1)
110+
omniauth (~> 1.0)
111+
rack-openid (~> 1.3.1)
112+
polyglot (0.3.3)
113+
pony (1.4)
114+
mail (> 2.0)
115+
pry (0.9.12.2)
116+
coderay (~> 1.0.5)
117+
method_source (~> 0.8)
118+
slop (~> 3.4)
119+
rack (1.4.1)
120+
rack-openid (1.3.1)
121+
rack (>= 1.1.0)
122+
ruby-openid (>= 2.1.8)
123+
rack-protection (1.2.0)
124+
rack
125+
rack-test (0.6.2)
126+
rack (>= 1.0)
127+
rake (0.9.2.2)
128+
rb-readline (0.4.2)
129+
redis (3.0.2)
130+
redis-namespace (1.2.1)
131+
redis (~> 3.0.0)
132+
resque (1.23.0)
133+
multi_json (~> 1.0)
134+
redis-namespace (~> 1.0)
135+
sinatra (>= 0.9.2)
136+
vegas (~> 0.1.2)
137+
resque-status (0.4.0)
138+
resque (~> 1.19)
139+
ruby-openid (2.2.2)
140+
sinatra (1.3.3)
141+
rack (~> 1.3, >= 1.3.6)
142+
rack-protection (~> 1.2)
143+
tilt (~> 1.3, >= 1.3.3)
144+
sinatra-contrib (1.3.2)
145+
backports (>= 2.0)
146+
eventmachine
147+
rack-protection
148+
rack-test
149+
sinatra (~> 1.3.0)
150+
tilt (~> 1.3)
151+
sinatra-flash (0.3.0)
152+
sinatra (>= 1.0.0)
153+
sinatra-reloader (1.0)
154+
sinatra-contrib
155+
slop (3.4.6)
156+
stringex (1.4.0)
157+
thin (1.5.0)
158+
daemons (>= 1.0.9)
159+
eventmachine (>= 0.12.6)
160+
rack (>= 1.0.0)
161+
thor (0.16.0)
162+
tilt (1.3.3)
163+
treetop (1.4.12)
164+
polyglot
165+
polyglot (>= 0.3.1)
166+
trollop (2.0)
167+
uuidtools (2.1.3)
168+
vegas (0.1.11)
169+
rack (>= 1.0.0)
170+
171+
PLATFORMS
172+
ruby
173+
174+
DEPENDENCIES
175+
activemodel
176+
bson_ext
177+
data_mapper
178+
dm-mysql-adapter
179+
dm-sqlite-adapter
180+
ffi
181+
foreman
182+
haml
183+
json
184+
nsconfig!
185+
omniauth
186+
omniauth-openid
187+
pony
188+
pry
189+
rake
190+
rb-readline
191+
rbvmomi!
192+
resque
193+
resque-status
194+
sinatra (>= 1.0)
195+
sinatra-flash
196+
sinatra-reloader
197+
thin

‎Procfile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
redis: redis-server > /dev/null
2+
web: bundle exec rackup -s thin -p 8080
3+
workers_deployer: bundle exec rake resque:workers --trace QUEUE=deployer COUNT=4
4+
workers_cloner: bundle exec rake resque:workers --trace QUEUE=cloner COUNT=4

‎README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# VMDeploy
2+
3+
Spin up VMware Virtual Machines from a simple web page.
4+
5+
No Windows required.

‎Rakefile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
$LOAD_PATH.unshift File.dirname(__FILE__)
2+
require 'resque/tasks'
3+
require 'vmdeploycommon'

‎TODO.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- capify
2+
- Deployer jobs should first of all check if a VM by that name alread exists.
3+
I don't think they can do that by simply querying vCenter
4+
- resque/status_server might need some (monkey)patching. Too many params screw the layout up...
5+
- VMDeploy::Exceptions module
6+
- log the "creator" somewhere! (it might be different than the "owner")

‎Vagrantfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# -*- mode: ruby -*-
2+
# vi: set ft=ruby :
3+
Vagrant.actions[:start].delete(Vagrant::Action::VM::ShareFolders)
4+
Vagrant::Config.run do |config|
5+
config.vm.box = "precise64"
6+
config.vm.define :vmdeploytemplate do |cfg|
7+
end
8+
end

‎config.ru

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$LOAD_PATH.unshift File.dirname(__FILE__)
2+
$stdout.sync = true
3+
4+
require 'vmdeploycommon'
5+
require 'vmdeploy/web'
6+
7+
run Rack::URLMap.new \
8+
"/" => VMDeploy::Web::App,
9+
"/resque" => Resque::Server.new

‎config/00-defaults.yaml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
DEFAULTS: &DEFAULTS
2+
datamapper_require: 'dm-sqlite-adapter'
3+
datamapper_adapter: 'sqlite3'
4+
datamapper_parameters: 'blah'
5+
support_email: 'it@blah.com'
6+
allowed_users_regexps:
7+
- '^.*@blah.com'
8+
- 'user@domain.com'
9+
pony_via: :sendmail
10+
pony_options: {}
11+
mailer_from: 'root@blah.com'
12+
mailer_recipients:
13+
- john@doe.com
14+
vcenter_cfg:
15+
:host: '192.168.123.123'
16+
:user: 'username'
17+
:password: 'password'
18+
:insecure: true
19+
:dcname: 'Blah'
20+
deployer_params:
21+
:ram_sizes:
22+
- '512MB'
23+
- '2GB'
24+
- '4GB'
25+
:number_of_cpus:
26+
- '1'
27+
- '2'
28+
:networks:
29+
- 'Internal'
30+
- 'Public'
31+
:ruby_versions:
32+
- 'ruby1.9=2:1.9.2p290'
33+
:departments:
34+
- 'dept1'
35+
- 'dept2'

‎data/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

‎public/bootstrap.min.css

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎public/deployer.png

51.6 KB
Loading

‎public/js/bootstrap.min.js

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎public/js/jquery-1.7.2.min.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎script/console.rb

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
$LOAD_PATH.unshift File.expand_path('..', File.dirname(__FILE__))
2+
require 'vmdeploycommon'
3+
VMDeploy::dm_setup
4+
5+
require 'pry'
6+
binding.pry

‎script/gen_test_ips.rb

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
$LOAD_PATH.unshift File.expand_path('..', File.dirname(__FILE__))
2+
require 'vmdeploycommon'
3+
VMDeploy::dm_setup
4+
require 'ipaddr'
5+
6+
fail "Refusing to run anywhere other than in development" unless VMDeploy[:environment] == 'development'
7+
8+
netname = 'Internal'
9+
net = IPAddr.new('192.168.123.0/24')
10+
from = net | IPAddr.new('0.0.0.10')
11+
to = net | IPAddr.new('0.0.0.20')
12+
13+
(from..to).each do |ip|
14+
ip = ip.to_s
15+
attributes = {
16+
:address => ip,
17+
:network => netname,
18+
:address => ip,
19+
:taken => false
20+
}
21+
begin
22+
VMDeploy::Models::IP.first_or_create(attributes).save
23+
rescue DataMapper::SaveFailureError => e
24+
puts e.resource.errors.inspect
25+
end
26+
end
27+

‎script/testdeploy.rb

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
$LOAD_PATH.unshift File.expand_path('..', File.dirname(__FILE__))
2+
require 'json'
3+
require 'vmdeploycommon'
4+
5+
params = JSON.parse <<EOJ
6+
{"vmname":"gtest",
7+
"owner":"giuliano.cioffi@forward.co.uk",
8+
"vmramsize":"512MB",
9+
"vmnumberofcpus":"1",
10+
"vmnetwork":"Internal",
11+
"rubyversion":"ruby1.9=2:1.9.2p290",
12+
"department":"uSwitch"}
13+
EOJ
14+
15+
job_id = VMDeploy::Jobs::Deployer::Vagrant.create(params)
16+
17+
puts "Created job: #{job_id}"

‎views/layout.haml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
!!!
2+
%html{:lang => 'en'}
3+
%head
4+
%title= @title || VMDeploy::ApplicationTitle
5+
%link{:href => '/bootstrap.min.css', :rel => 'stylesheet', :type => 'text/css'}
6+
:css
7+
.navbar .brand {
8+
-moz-transition: all 0.2s linear 0s;
9+
color: #AD3300;
10+
float: right;
11+
font-weight: bold;
12+
margin-left: 20px;
13+
padding-left: 0;
14+
padding-right: 0;
15+
text-shadow: 0 0 5px #AD3300;
16+
}
17+
img#splashimage { vertical-align: middle; }
18+
%link{:href => '/favicon.ico', :rel => 'shortcut icon'}
19+
%script{:type => 'text/javascript', :src => 'js/jquery-1.7.2.min.js'}
20+
%script{:type => 'text/javascript', :src => 'js/bootstrap.min.js'}
21+
%body
22+
.navbar
23+
.navbar-inner
24+
%a.brand{:href => '/'} vm-deploy
25+
%ul.nav
26+
- if authorized?
27+
%li
28+
%a{:href => '/resque', :target => '_blank'} Jobs Queue
29+
- if authorized?
30+
%a.btn.btn-small.pull-right{:href => '/logout'}Logout
31+
%p.navbar-text.pull-right #{current_user}
32+
-else
33+
%p.navbar-text.pull-right (not logged in)
34+
35+
#content= yield
36+
%footer
37+
.container
38+
.pull-left
39+
Brought to you by
40+
%a{:href => "mailto:#{VMDeploy[:support_email]}?subject=vmdeploy"} IT
41+
.pull-right= "(#{Sinatra::Base.environment.to_s} on #{request.env['SERVER_SOFTWARE'].gsub(/[ \/].*$/, '')})"
42+

‎views/root.haml

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
.container
2+
.row
3+
- if flash[:error]
4+
.alert.alert-error #{flash[:error]}
5+
- if flash[:warning]
6+
.alert #{flash[:warning]}
7+
- if flash[:notice]
8+
.alert.alert-success #{flash[:notice]}
9+
.hero-unit
10+
- if authorized?
11+
%form#buildvmform.form-horizontal{:action => '/build', :method => 'post'}
12+
%legend Create a New Virtual Machine
13+
- buildvmform_control_group(:vmname) do
14+
%label.control-label{:for => 'vmname'}VM Name
15+
.controls
16+
%input{:type => 'text', :name => 'vmname', :id => 'vmname', :value => buildvmform_vmname_value}
17+
- buildvmform_show_validation_error(:vmname)
18+
- buildvmform_control_group(:owner) do
19+
%label.control-label{:for => 'owner'}Owner's email
20+
.controls
21+
%input{:type => 'text', :name => 'owner', :id => 'owner', :value => (params[:owner] || session[:username])}
22+
- buildvmform_show_validation_error(:owner)
23+
- buildvmform_control_group(:vmramsize) do
24+
%label.control-label{:for => 'vmramsize'}Amount of RAM
25+
.controls
26+
#bgvmramsize.btn-group{'data-toggle' => 'buttons-radio'}
27+
- VMDeploy[:deployer_params][:ram_sizes].each do |ramsize|
28+
%button.btn{:type => 'button'} #{ramsize}
29+
%input{:type => 'hidden', :name => 'vmramsize', :id => 'vmramsize', :value => params[:vmramsize]}
30+
- buildvmform_show_validation_error(:vmramsize)
31+
- buildvmform_control_group(:vmnumberofcpus) do
32+
%label.control-label{:for => 'vmnumberofcpus'}Number of CPUs
33+
.controls
34+
#bgvmnumberofcpus.btn-group{'data-toggle' => 'buttons-radio'}
35+
- VMDeploy[:deployer_params][:number_of_cpus].each do |ncpus|
36+
%button.btn{:type => 'button'} #{ncpus}
37+
%input{:type => 'hidden', :name => 'vmnumberofcpus', :id => 'vmnumberofcpus', :value => params[:vmnumberofcpus]}
38+
- buildvmform_show_validation_error(:vmnumberofcpus)
39+
- buildvmform_control_group(:vmnetwork) do
40+
%label.control-label{:for => 'vmnetwork'}Network
41+
.controls
42+
#bgvmnetwork.btn-group{'data-toggle' => 'buttons-radio'}
43+
- VMDeploy[:deployer_params][:networks].each do |network|
44+
%button.btn{:type => 'button'} #{network}
45+
%input{:type => 'hidden', :name => 'vmnetwork', :id => 'vmnetwork', :value => params[:vmnetwork]}
46+
- buildvmform_show_validation_error(:vmnetwork)
47+
- buildvmform_control_group(:rubyversion) do
48+
%label.control-label{:for => 'rubyversion'}Ruby Version
49+
.controls
50+
%select{:name => 'rubyversion', :id => 'rubyversion'}
51+
- VMDeploy[:deployer_params][:ruby_versions].each do |ruby|
52+
%option{:value => ruby} #{ruby}
53+
- buildvmform_show_validation_error(:rubyversion)
54+
- buildvmform_control_group(:department) do
55+
%label.control-label{:for => 'department'}Department
56+
.controls
57+
%select{:name => 'department', :id => 'department'}
58+
- VMDeploy[:deployer_params][:departments].each do |dpt|
59+
%option{:value => dpt, :selected => params[:department] == dpt } #{dpt}
60+
- buildvmform_show_validation_error(:department)
61+
.control-group
62+
.controls
63+
#submit.btn.btn-primary{:type => 'submit'}Build VM
64+
#clear.btn Clear
65+
- else
66+
%a.btn.btn-primary.btn-large{:href => "/auth/google"} Login with Google Apps
67+
%img#splashimage.pull-right{:src => "/deployer.png"}
68+
%div{:style => 'clear: both;'}
69+
70+
:javascript
71+
$('#bgvmramsize > button').click(function(){
72+
$("#vmramsize").val($(this).text());
73+
});
74+
$('#bgvmnumberofcpus > button').click(function(){
75+
$("#vmnumberofcpus").val($(this).text());
76+
});
77+
$('#bgvmnetwork > button').click(function(){
78+
$("#vmnetwork").val($(this).text());
79+
});
80+
81+
function form_defaults() {
82+
if ($('#vmramsize').val() == '') {
83+
$('#vmramsize').val($('#bgvmramsize > button').first().text());
84+
}
85+
if ($('#vmnumberofcpus').val() == '') {
86+
$('#vmnumberofcpus').val($('#bgvmnumberofcpus > button').first().text());
87+
}
88+
if ($('#vmnetwork').val() == '') {
89+
$('#vmnetwork').val($('#bgvmnetwork > button').first().text());
90+
}
91+
}
92+
93+
function form_parameters_changed() {
94+
// "Click" the buttons according to the params
95+
v = $('#vmramsize').val(); $("#bgvmramsize > button:contains('"+v+"')").click();
96+
v = $('#vmnumberofcpus').val(); $("#bgvmnumberofcpus > button:contains('"+v+"')").click();
97+
v = $('#vmnetwork').val(); $("#bgvmnetwork > button:contains('"+v+"')").click();
98+
}
99+
100+
$(document).ready(function() {
101+
$('#submit.btn').click(function(){
102+
$('#buildvmform').submit();
103+
});
104+
$('#clear.btn').click(function(){
105+
$('#vmramsize').val('');
106+
$('#vmnumberofcpus').val('');
107+
$('#vmnetwork').val('');
108+
form_defaults();
109+
form_parameters_changed();
110+
});
111+
form_defaults();
112+
form_parameters_changed();
113+
})

‎vmdeploy/deployerparams.rb

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require 'active_model'
2+
3+
module VMDeploy
4+
class DeployerParams
5+
include ActiveModel::Validations
6+
include ActiveModel::Conversion
7+
extend ActiveModel::Naming
8+
9+
attr_accessor :vmname, :owner, :vmramsize, :vmnumberofcpus,
10+
:vmnetwork, :rubyversion, :department
11+
12+
validates_presence_of :vmname, :owner, :vmramsize, :vmnumberofcpus,
13+
:vmnetwork, :rubyversion, :department
14+
15+
validates :vmname, :format => {:with => /^[a-z\d\-]+$/i, :message => 'contains invalid characters'}, :length => {:minimum => 3, :message => 'is too short'}
16+
validates :owner, :format => {:with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i, :message => 'doesn\'t look like a valid email address'}
17+
validates :vmramsize, :inclusion => {:in => VMDeploy[:deployer_params][:ram_sizes], :message => 'is invalid'}
18+
validates :vmnumberofcpus, :inclusion => {:in => VMDeploy[:deployer_params][:number_of_cpus], :message => 'is invalid'}
19+
validates :vmnetwork, :inclusion => {:in => VMDeploy[:deployer_params][:networks], :message => 'is invalid'}
20+
validates :rubyversion, :inclusion => {:in => VMDeploy[:deployer_params][:ruby_versions], :message => 'is invalid'}
21+
validates :department, :inclusion => {:in => VMDeploy[:deployer_params][:departments], :message => 'is invalid'}
22+
23+
def initialize(params=nil)
24+
params.each do |name, value|
25+
send("#{name}=", value)
26+
end unless params.nil?
27+
end
28+
29+
def persisted?
30+
false
31+
end
32+
33+
# Resque Status expects a Hash
34+
def to_hash
35+
Hash[(instance_variables - [:@validation_context, :@errors]).map {|v| [v.to_s[1..-1],instance_variable_get(v)]}]
36+
end
37+
end
38+
end

‎vmdeploy/jobs.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require 'resque'
2+
require 'resque-status'
3+
4+
module VMDeploy::Jobs
5+
end
6+
module VMDeploy::Jobs::Deployer
7+
end
8+
9+
require 'vmdeploy/jobs/deployer/shared'
10+
require 'vmdeploy/jobs/deployer/fake'
11+
require 'vmdeploy/jobs/deployer/vmware'
12+
require 'vmdeploy/jobs/deployer/vagrant' if VMDeploy[:environment] == 'development'

‎vmdeploy/jobs/deployer/fake.rb

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module VMDeploy::Jobs::Deployer
2+
class Fake
3+
include VMDeploy::Loggr
4+
include VMDeploy::Jobs::Deployer::Shared
5+
include Resque::Plugins::Status
6+
@queue = :deployer
7+
8+
def initialize(uuid, options={})
9+
super uuid, options
10+
log_setup(File.join(VMDeploy::LogDir,'deployer.log'),self.class.to_s,uuid.to_s)
11+
end
12+
13+
def perform
14+
total = 30
15+
len = States.length
16+
States.each_index do |i|
17+
if States[i][:key] == 'start'
18+
log.info States[i][:message] + ' -- ' + options.to_json
19+
else
20+
log.info States[i][:message]
21+
end
22+
progress_state_index(i)
23+
sleep(Float(total)/len)
24+
end
25+
end
26+
27+
def name
28+
"#{options['vmname']}/#{options['owner']}"
29+
end
30+
end
31+
end

‎vmdeploy/jobs/deployer/shared.rb

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module VMDeploy::Jobs::Deployer
2+
module Shared
3+
States = [
4+
{ :key => 'start', :message => 'Deployment started'},
5+
{ :key => 'viserver_connect', :message => 'Connecting to the vCenter server'},
6+
{ :key => 'vm_check_existence', :message => 'Checking if the chosen VM name already exists on vCenter'},
7+
{ :key => 'pool_vm_get', :message => 'Getting hold of a free pool VM'},
8+
{ :key => 'pool_vm_bootstrap', :message => 'Bootstrapping OS'},
9+
{ :key => 'vm_shutdown', :message => 'Shutting VM down'},
10+
{ :key => 'pool_vm_tweak_hardware', :message => 'Changing VM hardware/network properties'},
11+
{ :key => 'vm_start', :message => 'Starting up VM'},
12+
{ :key => 'vm_wait_poweron', :message => 'Waiting until the VM is on'},
13+
{ :key => 'vm_check_bootstrap_successful', :message => 'Checking if Bootstrap succeeded'},
14+
{ :key => 'notify_owner', :message => 'Deploy looks ok, notifying the Owner'},
15+
{ :key => 'vm_vmotion', :message => 'Moving VM to production datastore'},
16+
{ :key => 'viserver_disconnect', :message => 'Disconnecting from the vCenter server'},
17+
{ :key => 'done', :message => 'Deployment finished'}
18+
]
19+
StatesK2I = Hash[States.each_with_index.map {|s,i| [s[:key],i]}]
20+
21+
def get_message_by_key(key)
22+
States[StatesK2I[key]][:message]
23+
end
24+
25+
def progress_state_index(i)
26+
at((Float(i)*100/States.length).round, 100, States[i][:message])
27+
end
28+
29+
def progress_state_key(key)
30+
progress_state_index(StatesK2I[key])
31+
end
32+
end
33+
end

‎vmdeploy/jobs/deployer/vagrant.rb

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# This is for testing only.
2+
# - It expects a Vagrant VM to already exist
3+
# - The VM must be called "vmdeploytemplate". Example Vagrantfile:
4+
# Vagrant.actions[:start].delete(Vagrant::Action::VM::ShareFolders)
5+
# Vagrant::Config.run do |config|
6+
# config.vm.box = "precise64"
7+
# config.vm.define :vmdeploytemplate do |cfg|
8+
# end
9+
# end
10+
# - This deployer class will attempt to start the VM, then bootstrap it with Puppet.
11+
module VMDeploy::Jobs::Deployer
12+
class Vagrant
13+
include VMDeploy::Loggr
14+
include VMDeploy::Jobs::Deployer::Shared
15+
include Resque::Plugins::Status
16+
@queue = :deployer
17+
18+
def initialize(uuid, options={})
19+
super uuid, options
20+
log_setup(File.join(VMDeploy::LogDir,'deployer.log'),self.class.to_s,uuid.to_s)
21+
end
22+
23+
def bailout(message)
24+
log.error message
25+
fail message
26+
end
27+
28+
def get_vm_state
29+
state = nil
30+
IO.popen('vagrant status') do |f|
31+
f.each_line do |l|
32+
if m = l.match(/^vmdeploytemplate.*(running|poweroff)$/)
33+
state = m[1]
34+
break
35+
end
36+
end
37+
end
38+
bailout "Can't seem to run/understand 'vagrant status'" if state.nil?
39+
bailout "Unknown status returned by 'vagrant status'" unless state == 'running' or state == 'poweroff'
40+
return (state == 'running') ? 'on' : 'off'
41+
end
42+
43+
def get_ssh_params
44+
params = {}
45+
IO.popen('vagrant ssh-config') do |f|
46+
f.each_line do |l|
47+
l.sub!(/^ */,'')
48+
l.chomp!
49+
k,v = l.split(/ +/,2)
50+
params[k] = v
51+
end
52+
end
53+
params
54+
end
55+
56+
def perform
57+
log.info get_message_by_key('start') + ' -- ' + options.to_json
58+
progress_state_key('start')
59+
progress_state_key('pool_vm_get')
60+
vm_state = get_vm_state
61+
log.info "VM is #{vm_state}"
62+
if vm_state == 'off'
63+
log.info 'Attempting to power VM up'
64+
IO.popen('vagrant up') { |f| f.readlines }
65+
vm_state = get_vm_state
66+
bailout "VM isn't on" unless vm_state == 'on'
67+
log.info "VM is #{vm_state}"
68+
end
69+
log.info "Obatainig SSH parameters"
70+
ssh_params = get_ssh_params
71+
log.info ssh_params.inspect
72+
progress_state_key('done')
73+
rescue Exception => e
74+
log.error e.message
75+
raise
76+
end
77+
78+
def name
79+
"#{options['vmname']}/#{options['owner']}"
80+
end
81+
end
82+
end

‎vmdeploy/jobs/deployer/vmware.rb

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
require 'rbvmomi'
2+
3+
module VMDeploy::Jobs::Deployer
4+
class VMWare
5+
include VMDeploy::Loggr
6+
include VMDeploy::Jobs::Deployer::Shared
7+
include Resque::Plugins::Status
8+
@queue = :deployer
9+
10+
def initialize(uuid, options={})
11+
super uuid, options
12+
log_setup(File.join(VMDeploy::LogDir,'deployer.log'),self.class.to_s,uuid.to_s)
13+
end
14+
15+
def bailout(message)
16+
log.error message
17+
fail message
18+
end
19+
20+
def find_vms_by_name(term)
21+
if term.is_a? String
22+
matcher = lambda {|term,value| term == value}
23+
elsif term.is_a? Regexp
24+
matcher = lambda {|term,value| term.match(value)}
25+
else
26+
fail "\"term\" must be a String or a Regexp"
27+
end
28+
filterSpec = RbVmomi::VIM.PropertyFilterSpec(
29+
:objectSet => [
30+
:obj => @dc.vmFolder,
31+
:skip => true,
32+
:selectSet => [
33+
RbVmomi::VIM.TraversalSpec(
34+
:name => 'VisitFolders',
35+
:type => 'Folder',
36+
:path => 'childEntity',
37+
:skip => false,
38+
:selectSet => [
39+
RbVmomi::VIM.SelectionSpec(:name => 'VisitFolders')
40+
]
41+
)
42+
]
43+
],
44+
:propSet => [{ :type => 'VirtualMachine', :pathSet => ['name'] }]
45+
)
46+
data = @vim.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
47+
return [] if data.nil?
48+
data.find_all {|d| d.props[:propSet].find {|p| p.name == 'name' && matcher.call(term,p.val)}}.map {|r| r.obj}
49+
end
50+
51+
def find_vm_by_name(term)
52+
vm = find_vms_by_name(term)
53+
fail "More than one VM returned, use find_vms_by_name instead" if vm.length > 1
54+
return vm.first
55+
end
56+
57+
# TODO: find_pool_templates relies on this patch to "lib/rbvmomi/vim/Folder.rb"
58+
# I'll create a pull request for it
59+
# def findByInventoryPath path
60+
# propSpecs = {
61+
# :entity => self, :inventoryPath => path
62+
# }
63+
# x = _connection.searchIndex.FindByInventoryPath(propSpecs)
64+
# end
65+
def find_pool_templates(term)
66+
fail "\"term\" must be a Regexp" unless term.is_a? Regexp
67+
# All the templates/pool servers must be in a folder named "Pool"
68+
# Subfolders won't be searched
69+
pool_folder = @dc.vmFolder.findByInventoryPath('/Qube/vm/Pool')
70+
fail "can't find \"Pool\" folder" if pool_folder.nil?
71+
filterSpec = RbVmomi::VIM.PropertyFilterSpec(
72+
:objectSet => [
73+
:obj => pool_folder,
74+
:skip => true,
75+
:selectSet => [
76+
RbVmomi::VIM.TraversalSpec(
77+
:name => 'VisitFolders',
78+
:type => 'Folder',
79+
:path => 'childEntity',
80+
:skip => false
81+
)
82+
]
83+
],
84+
:propSet => [{ :type => 'VirtualMachine', :pathSet => %w(name runtime.powerState guest.ipAddress) }]
85+
)
86+
data = @vim.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
87+
return [] if data.nil?
88+
vms = data.find_all {|d| d.props[:propSet].find {|p| p.name == 'name' && term.match(p.val)}}.map {|r| r.obj}
89+
vms.find_all {|vm| vm.customValue.find {|p| p.key == 2}.value == 'pool'} # 2 is "Creator"
90+
end
91+
92+
# DRY what?
93+
def perform
94+
log.info get_message_by_key('start') + ' -- ' + options.to_json
95+
progress_state_key('start')
96+
@vc_cfg = YAML.load_file(File.join(File.dirname(__FILE__),'..','..','..','config','vcenter.yml'))
97+
bailout "VM params do not validate" unless VMDeploy::DeployerParams.new(options).valid?
98+
99+
log.info get_message_by_key('viserver_connect')
100+
progress_state_key('viserver_connect')
101+
@vim = RbVmomi::VIM.connect host: VMDeploy[:vcenter_cfg][:host],
102+
user: VMDeploy[:vcenter_cfg][:user],
103+
password: VMDeploy[:vcenter_cfg][:password],
104+
insecure: VMDeploy[:vcenter_cfg][:insecure]
105+
bailout "Can't connect to vCenter" unless @vim
106+
@dc = @vim.serviceInstance.find_datacenter(VMDeploy[:vcenter_cfg][:dcname]) or bailout 'datacenter not found'
107+
108+
log.info get_message_by_key('vm_check_existence')
109+
progress_state_key('vm_check_existence')
110+
bailout "A VM by this name already exists" if find_vm_by_name(options['vmname'])
111+
112+
log.info get_message_by_key('pool_vm_get')
113+
progress_state_key('pool_vm_get')
114+
pool_vms = find_pool_templates(/^bbpool/) #TODO: this has to come from the Form
115+
bailout "No free pool servers are available" unless pool_vms.length > 0
116+
pool_vm = pool_vms.last
117+
log.info "Deploying from \"#{pool_vm.name}\", #{pool_vms.length - 1} pool VM(s) left"
118+
119+
log.info get_message_by_key('pool_vm_bootstrap')
120+
progress_state_key('pool_vm_get')
121+
bailout "The chosen pool server is not powered on, this is unexpected" if pool_vm.runtime.powerState != 'poweredOn'
122+
bailout "\"#{pool_vm.guest.ipAddress}\" doesn't look like an IP address" unless
123+
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/.match(pool_vm.guest.ipAddress)
124+
log.info "Pool server IP is #{pool_vm.guest.ipAddress}"
125+
126+
#TODO: check if the IP is on the Dev network
127+
128+
log.info get_message_by_key('done')
129+
progress_state_key('done')
130+
rescue Exception => e
131+
log.error e.message
132+
raise
133+
end
134+
135+
def name
136+
"#{options['vmname']}/#{options['owner']}"
137+
end
138+
end
139+
end

‎vmdeploy/loggr.rb

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
require 'logger'
2+
module VMDeploy
3+
module Loggr
4+
def log_setup(filename,classname,id)
5+
@logger = Logger.new(filename, File::WRONLY | File::APPEND)
6+
@logger.formatter = proc do |severity, datetime, progname, msg|
7+
"#{datetime.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d" % datetime.usec} #{classname} [#{id}] #{severity} #{msg}\n"
8+
end
9+
end
10+
11+
def log
12+
@logger
13+
end
14+
end
15+
end

‎vmdeploy/models.rb

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require 'data_mapper'
2+
module VMDeploy::Models
3+
class IP
4+
include DataMapper::Resource
5+
property :id, Serial
6+
property :network, String, :required => true
7+
property :address, String, :required => true, :unique => true
8+
property :taken, Boolean, :index => true
9+
property :taken_by_vm, String
10+
property :taken_by_jobid, String
11+
property :taken_on, String
12+
end
13+
DataMapper.auto_upgrade!
14+
end

‎vmdeploy/postinit.rb

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require 'vmdeploy/deployerparams'
2+
require 'vmdeploy/loggr'
3+
require 'vmdeploy/jobs'

‎vmdeploy/preinit.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'nsconfig'
2+
3+
module VMDeploy
4+
extend NSConfig
5+
6+
def self.preconfig base_path
7+
VMDeploy.config_path = File.join(base_path,'config')
8+
if VMDeploy.get_environment == 'development'
9+
VMDeploy[:datamapper_parameters] = File.join(base_path,VMDeploy[:datamapper_parameters])
10+
end
11+
end
12+
13+
def self.dm_setup
14+
require 'data_mapper'
15+
require VMDeploy[:datamapper_require]
16+
DataMapper.setup(:default, "#{VMDeploy[:datamapper_adapter]}://#{VMDeploy[:datamapper_parameters]}")
17+
DataMapper::Model.raise_on_save_failure = true
18+
require 'vmdeploy/models'
19+
end
20+
end

‎vmdeploy/web.rb

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
require 'sinatra/base'
2+
require 'sinatra/flash'
3+
require 'resque/server'
4+
require 'resque/status_server'
5+
require 'openid/store/filesystem'
6+
require 'omniauth'
7+
require 'omniauth-openid'
8+
require 'active_model'
9+
10+
module VMDeploy::Web
11+
$0 = self.name
12+
VMDeploy::dm_setup
13+
14+
class App < Sinatra::Base
15+
16+
configure :production, :development do
17+
register Sinatra::Flash
18+
enable :logging
19+
set :root, VMDeploy::SinatraRoot
20+
set :views, File.join(VMDeploy::SinatraRoot,'views')
21+
use Rack::Session::Cookie
22+
set :session_secret, "nothingtoseehere"
23+
use OmniAuth::Builder do
24+
provider :open_id, store: OpenID::Store::Filesystem.new('/tmp'), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id'
25+
end
26+
end
27+
28+
configure :development do
29+
require 'sinatra/reloader'
30+
register Sinatra::Reloader
31+
end
32+
33+
helpers do
34+
def authorized?
35+
session[:authenticated]
36+
end
37+
38+
def protected
39+
unless authorized?
40+
flash[:warning] = 'The page cannot be viewed without first logging in...'
41+
redirect '/'
42+
end
43+
authorized?
44+
end
45+
46+
def protected!
47+
throw(:halt, [401, "Not authorized\n"]) unless authorized?
48+
end
49+
50+
def current_user
51+
return nil unless authorized?
52+
session[:username]
53+
end
54+
55+
def partial( page, variables={} )
56+
haml page.to_sym, {layout:false}, variables
57+
end
58+
59+
def buildvmform_vmname_value
60+
coming_from_build = session[:old_path_info] && (session[:old_path_info] == '/build')
61+
if coming_from_build && @dparams.errors.messages[:vmname]
62+
params[:vmname] || ''
63+
else
64+
''
65+
end
66+
end
67+
68+
def buildvmform_control_group(param)
69+
coming_from_build = session[:old_path_info] && (session[:old_path_info] == '/build')
70+
haml_tag :div, :class => ('control-group' + ((coming_from_build && @dparams.errors.messages[param]) ? ' error' : '')) do
71+
yield
72+
end
73+
end
74+
75+
def buildvmform_show_validation_error(param)
76+
coming_from_build = session[:old_path_info] && (session[:old_path_info] == '/build')
77+
if coming_from_build && @dparams.errors.messages[param]
78+
haml_tag 'span.help-inline', @dparams.errors.messages[param].first
79+
end
80+
end
81+
end
82+
83+
# authentication callback
84+
[:get, :post].each do |method|
85+
send method, '/auth/:provider/callback' do
86+
session[:username] = request.env['omniauth.auth']['info']['email']
87+
if VMDeploy[:allowed_users_regexps].any? {|re| Regexp.new(re).match(session[:username])}
88+
session[:authenticated] = true
89+
flash[:notice] = 'Login successful'
90+
redirect '/'
91+
else
92+
session[:authenticated] = false
93+
flash[:error] = "You don't have permission to log in, sorry..."
94+
redirect '/'
95+
end
96+
end
97+
end
98+
99+
get '/logout' do
100+
session[:authenticated] = false
101+
session.clear
102+
flash[:notice] = "You've been successfully logged out"
103+
redirect '/'
104+
end
105+
106+
get '/auth/failure' do
107+
session[:authenticated] = false
108+
flash[:error] = "Authentication failed (#{params[:message]})"
109+
redirect '/'
110+
end
111+
112+
# root page
113+
get '/' do
114+
if session.include? :old_build_params
115+
params.merge! JSON.parse(session[:old_build_params])
116+
session.delete :old_build_params
117+
end
118+
@dparams = VMDeploy::DeployerParams.new params
119+
@dparams.valid? # we don't really care about the result
120+
haml :root
121+
end
122+
123+
post '/build' do
124+
protected
125+
@dparams = VMDeploy::DeployerParams.new params
126+
if @dparams.valid?
127+
job_id = VMDeploy::Jobs::Deployer::Fake.create(@dparams.to_hash)
128+
if job_id
129+
flash[:notice] = "Your VM is being built. Progress info is available <a href=\"/resque/statuses/#{job_id}\" target=\"_blank\">here</a>."
130+
else
131+
flash[:error] = "<strong>Couldn't submit a deploy job</strong>"
132+
end
133+
else
134+
flash[:error] = '<strong>One or more errors occurred</strong>'
135+
end
136+
session[:old_build_params] = params.to_json
137+
redirect '/'
138+
end
139+
140+
['/', '/build'].each do |path|
141+
after path do
142+
session[:old_path_info] = request.path_info
143+
end
144+
end
145+
end
146+
end

‎vmdeploycommon.rb

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require 'vmdeploy/preinit'
2+
VMDeploy::preconfig File.dirname(__FILE__)
3+
VMDeploy::SinatraRoot = File.dirname(__FILE__)
4+
VMDeploy::LogDir = File.join(File.dirname(__FILE__),'log')
5+
VMDeploy::ApplicationTitle = 'VMDeploy'
6+
require 'vmdeploy/postinit'

0 commit comments

Comments
 (0)
Please sign in to comment.