standup 0.2.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.
- data/.gitignore +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +8 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/lib/standup/core_ext.rb +11 -0
- data/lib/standup/ec2/base.rb +61 -0
- data/lib/standup/ec2/elastic_ip.rb +49 -0
- data/lib/standup/ec2/instance.rb +80 -0
- data/lib/standup/ec2/security_group.rb +95 -0
- data/lib/standup/ec2/volume.rb +57 -0
- data/lib/standup/ec2.rb +5 -0
- data/lib/standup/node.rb +48 -0
- data/lib/standup/railtie.rb +10 -0
- data/lib/standup/remoting.rb +133 -0
- data/lib/standup/scripts/base.rb +52 -0
- data/lib/standup/settings.rb +16 -0
- data/lib/standup.rb +54 -0
- data/lib/tasks/standup.rake +19 -0
- data/scripts/allocate_ip.rb +7 -0
- data/scripts/basics.rb +6 -0
- data/scripts/ec2.rb +60 -0
- data/scripts/generate/script.rb +7 -0
- data/scripts/generate.rb +14 -0
- data/scripts/init/standup.yml +17 -0
- data/scripts/init.rb +7 -0
- data/scripts/localize.rb +18 -0
- data/scripts/passenger/nginx.conf +32 -0
- data/scripts/passenger/upstart.conf +10 -0
- data/scripts/passenger.rb +31 -0
- data/scripts/postgresql/postgresql.conf +501 -0
- data/scripts/postgresql.rb +24 -0
- data/scripts/ruby.rb +12 -0
- data/scripts/setup.rb +11 -0
- data/scripts/status.rb +15 -0
- data/scripts/terminate.rb +12 -0
- data/scripts/update.rb +15 -0
- data/scripts/webapp/nginx-server-fragment.conf +12 -0
- data/scripts/webapp.rb +79 -0
- data/standup.gemspec +95 -0
- metadata +198 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Ilia Ablamonov, Cloud Castle Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = 'standup'
|
8
|
+
gem.summary = %Q{Standup is an application deployment and infrastructure management tool for Rails and Amazon EC2}
|
9
|
+
gem.description = %Q{}
|
10
|
+
gem.email = 'ilia@flamefork.ru'
|
11
|
+
gem.homepage = 'http://github.com/Flamefork/standup'
|
12
|
+
gem.authors = ['Ilia Ablamonov', 'Cloud Castle Inc.']
|
13
|
+
gem.add_dependency 'activesupport', '>= 3.0'
|
14
|
+
gem.add_dependency 'settingslogic', '>= 2.0'
|
15
|
+
gem.add_dependency 'amazon-ec2', '>= 0.9'
|
16
|
+
gem.add_dependency 'aws-s3', '>= 0.5'
|
17
|
+
gem.add_dependency 'net-ssh', '>= 2.0'
|
18
|
+
gem.add_dependency 'highline', '>= 1.5.2'
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Standup
|
2
|
+
module EC2
|
3
|
+
class Base
|
4
|
+
def initialize info = false
|
5
|
+
case info
|
6
|
+
when Hash
|
7
|
+
set_info info
|
8
|
+
when true
|
9
|
+
load_info
|
10
|
+
when false
|
11
|
+
# nothing
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.info_reader *names
|
16
|
+
names.each do |name|
|
17
|
+
class_eval "def #{name}; read_info_field :#{name}; end", __FILE__, __LINE__
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?
|
22
|
+
read_info_field :exists
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_info; end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def read_info_field name
|
30
|
+
load_info unless instance_variable_defined?(:"@#{name}")
|
31
|
+
instance_variable_set(:"@#{name}", nil) unless instance_variable_defined?(:"@#{name}")
|
32
|
+
@exists = true
|
33
|
+
instance_variable_get(:"@#{name}")
|
34
|
+
rescue AWS::InvalidGroupNotFound
|
35
|
+
rescue AWS::InvalidInstanceIDNotFound
|
36
|
+
rescue AWS::InvalidVolumeIDNotFound
|
37
|
+
@exists = false
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_info info
|
42
|
+
info.each do |key, value|
|
43
|
+
instance_variable_set :"@#{key}", value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def list
|
48
|
+
self.class.list
|
49
|
+
end
|
50
|
+
|
51
|
+
def api
|
52
|
+
self.class.api
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.api
|
56
|
+
@@api ||= AWS::EC2::Base.new :access_key_id => Settings.aws.access_key_id,
|
57
|
+
:secret_access_key => Settings.aws.secret_access_key
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Standup
|
2
|
+
module EC2
|
3
|
+
class ElasticIP < Base
|
4
|
+
def initialize ip, info = false
|
5
|
+
@ip = ip
|
6
|
+
super info
|
7
|
+
end
|
8
|
+
|
9
|
+
info_reader :ip, :attached_to
|
10
|
+
|
11
|
+
def self.list reload = false
|
12
|
+
if !class_variable_defined?(:@@list) || reload
|
13
|
+
@@list = {}
|
14
|
+
result = api.describe_addresses
|
15
|
+
result.addressesSet.item.each do |item|
|
16
|
+
@@list[item.publicIp] = new item.publicIp, :attached_to => Instance.new(item.instanceId)
|
17
|
+
end if result.addressesSet
|
18
|
+
end
|
19
|
+
@@list
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create
|
23
|
+
ip = api.allocate_address.publicIp
|
24
|
+
list[ip] = ElasticIP.new ip
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy
|
28
|
+
api.release_address :public_ip => @ip
|
29
|
+
list.delete @ip
|
30
|
+
end
|
31
|
+
|
32
|
+
def attach_to instance
|
33
|
+
api.associate_address :instance_id => instance.id,
|
34
|
+
:public_ip => @ip
|
35
|
+
@attached_to = instance
|
36
|
+
end
|
37
|
+
|
38
|
+
def detach
|
39
|
+
api.disassociate_address :public_ip => @ip
|
40
|
+
@attached_to = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_info
|
44
|
+
result = api.describe_addresses :public_ip => @ip
|
45
|
+
@attached_to = Instance.new(result.addressesSet.item[0].instanceId)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Standup
|
2
|
+
module EC2
|
3
|
+
class Instance < Base
|
4
|
+
def initialize id, info = false
|
5
|
+
@id = id
|
6
|
+
super info
|
7
|
+
end
|
8
|
+
|
9
|
+
info_reader :id, :external_ip, :internal_ip, :state, :security_groups, :architecture
|
10
|
+
|
11
|
+
def self.list reload = false
|
12
|
+
if !class_variable_defined?(:@@list) || reload
|
13
|
+
@@list = {}
|
14
|
+
result = api.describe_instances
|
15
|
+
result.reservationSet.item.each do |ritem|
|
16
|
+
ritem.instancesSet.item.each do |item|
|
17
|
+
@@list[item.instanceId] = new item.instanceId, build_info(ritem, item)
|
18
|
+
end if ritem.instancesSet
|
19
|
+
end if result.reservationSet
|
20
|
+
end
|
21
|
+
@@list
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.group_running reload = false
|
25
|
+
result = {}
|
26
|
+
SecurityGroup.list(reload).each {|name, _| result[name] = []}
|
27
|
+
list(reload).each do |_, instance|
|
28
|
+
instance.security_groups.each do |sg|
|
29
|
+
result[sg.name] << instance
|
30
|
+
end unless [:terminated, :"shutting-down"].include?(instance.state)
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create image_id, instance_type, security_groups
|
36
|
+
response = api.run_instances :image_id => image_id,
|
37
|
+
:key_name => Settings.aws.keypair_name,
|
38
|
+
:instance_type => instance_type,
|
39
|
+
:security_group => security_groups.map(&:name),
|
40
|
+
:availability_zone => Settings.aws.availability_zone
|
41
|
+
id = response.instancesSet.item[0].instanceId
|
42
|
+
list[id] = Instance.new id
|
43
|
+
end
|
44
|
+
|
45
|
+
def terminate
|
46
|
+
api.terminate_instances :instance_id => @id
|
47
|
+
end
|
48
|
+
|
49
|
+
def reboot
|
50
|
+
api.reboot_instances :instance_id => @id
|
51
|
+
end
|
52
|
+
|
53
|
+
def wait_until timeout = 300
|
54
|
+
sleeping = 0
|
55
|
+
while yield(self) && sleeping < timeout
|
56
|
+
sleeping += sleep 5
|
57
|
+
STDOUT.print '.'
|
58
|
+
STDOUT.flush
|
59
|
+
load_info
|
60
|
+
end
|
61
|
+
print "\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_info
|
65
|
+
ritem = api.describe_instances(:instance_id => @id).reservationSet.item[0]
|
66
|
+
set_info self.class.build_info(ritem, ritem.instancesSet.item[0])
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def self.build_info ritem, item
|
72
|
+
return :external_ip => item.ipAddress,
|
73
|
+
:internal_ip => item.privateIpAddress,
|
74
|
+
:state => item.instanceState.name.to_sym,
|
75
|
+
:architecture => item.architecture,
|
76
|
+
:security_groups => ritem.groupSet.item.map{|i| SecurityGroup.new(i.groupId)}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Standup
|
2
|
+
module EC2
|
3
|
+
class SecurityGroup < Base
|
4
|
+
IPRule = Struct.new(:ip, :protocol, :from_port, :to_port)
|
5
|
+
|
6
|
+
def initialize name, info = false
|
7
|
+
@name = name
|
8
|
+
super info
|
9
|
+
end
|
10
|
+
|
11
|
+
info_reader :name, :description, :rules
|
12
|
+
|
13
|
+
def self.list reload = false
|
14
|
+
if !class_variable_defined?(:@@list) || reload
|
15
|
+
@@list = {}
|
16
|
+
result = api.describe_security_groups
|
17
|
+
result.securityGroupInfo.item.each do |gitem|
|
18
|
+
@@list[gitem.groupName] = new gitem.groupName, build_info(gitem)
|
19
|
+
end if result.securityGroupInfo
|
20
|
+
end
|
21
|
+
@@list
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.create name, description = name
|
25
|
+
api.create_security_group :group_name => name,
|
26
|
+
:group_description => description
|
27
|
+
list[name] = SecurityGroup.new name,
|
28
|
+
:description => description,
|
29
|
+
:rules => []
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete
|
33
|
+
api.delete_security_group :group_name => @name
|
34
|
+
list.delete @name
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_rule rule
|
38
|
+
api.authorize_security_group_ingress build_rules_opts(rule)
|
39
|
+
rules << rule
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_rule rule
|
43
|
+
api.revoke_security_group_ingress build_rules_opts(rule)
|
44
|
+
rules.delete rule
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_info
|
48
|
+
result = api.describe_security_groups :group_name => [@name]
|
49
|
+
set_info self.class.build_info(result.securityGroupInfo.item[0])
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash
|
53
|
+
@name.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql? other
|
57
|
+
@name == other.name
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def build_rules_opts rule
|
63
|
+
case rule
|
64
|
+
when SecurityGroup
|
65
|
+
return :group_name => @name,
|
66
|
+
:source_security_group_name => rule.name,
|
67
|
+
:source_security_group_owner_id => Settings.aws.account_id
|
68
|
+
when IPRule
|
69
|
+
return :group_name => @name,
|
70
|
+
:ip_protocol => rule.protocol,
|
71
|
+
:from_port => rule.from_port,
|
72
|
+
:to_port => rule.to_port,
|
73
|
+
:cidr_ip => rule.ip
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.build_info gitem
|
78
|
+
rules = Set.new
|
79
|
+
gitem.ipPermissions.item.each do |pitem|
|
80
|
+
rules << if pitem.groups
|
81
|
+
SecurityGroup.new pitem.groups.item[0].groupName
|
82
|
+
else
|
83
|
+
IPRule.new pitem.ipRanges.item[0].cidrIp,
|
84
|
+
pitem.ipProtocol,
|
85
|
+
pitem.fromPort,
|
86
|
+
pitem.toPort
|
87
|
+
end
|
88
|
+
end if gitem.ipPermissions
|
89
|
+
|
90
|
+
return :description => gitem.groupDescription,
|
91
|
+
:rules => rules
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Standup
|
2
|
+
module EC2
|
3
|
+
class Volume < Base
|
4
|
+
def initialize id, info = false
|
5
|
+
@id = id
|
6
|
+
super info
|
7
|
+
end
|
8
|
+
|
9
|
+
info_reader :id, :attached_to
|
10
|
+
|
11
|
+
def self.list reload = false
|
12
|
+
if !class_variable_defined?(:@@list) || reload
|
13
|
+
@@list = {}
|
14
|
+
response = api.describe_volumes
|
15
|
+
response.volumeSet.item.each do |item|
|
16
|
+
instance = item.attachmentSet ? Instance.new(item.attachmentSet.item[0].instanceId) : nil
|
17
|
+
@@list[item.volumeId] = Volume.new item.volumeId,
|
18
|
+
:status => item.status.to_sym,
|
19
|
+
:attached_to => instance
|
20
|
+
end if response.volumeSet
|
21
|
+
end
|
22
|
+
@@list
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.create size
|
26
|
+
response = api.create_volume :size => size.to_s,
|
27
|
+
:availability_zone => Settings.aws.availability_zone
|
28
|
+
list[response.volumeId] = Volume.new response.volumeId
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
api.delete_volume :volume_id => @id
|
33
|
+
list.delete @id
|
34
|
+
end
|
35
|
+
|
36
|
+
def attach_to instance, device
|
37
|
+
api.attach_volume :volume_id => @id,
|
38
|
+
:instance_id => instance.id,
|
39
|
+
:device => device
|
40
|
+
@attached_to = instance
|
41
|
+
end
|
42
|
+
|
43
|
+
def detach
|
44
|
+
api.detach_volume :volume_id => @id,
|
45
|
+
:force => 'true'
|
46
|
+
@attached_to = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_info
|
50
|
+
response = api.describe_volumes :volume_id => @id
|
51
|
+
item = response.volumeSet.item[0]
|
52
|
+
@status = item.status.to_sym
|
53
|
+
@attached_to = item.attachmentSet ? Instance.new(item.attachmentSet.item[0].instanceId) : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/standup/ec2.rb
ADDED
data/lib/standup/node.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Standup
|
2
|
+
class Node
|
3
|
+
def initialize name
|
4
|
+
@name = name
|
5
|
+
|
6
|
+
@scripts = ActiveSupport::HashWithIndifferentAccess.new
|
7
|
+
Standup.scripts.each do |sname, script|
|
8
|
+
@scripts[sname] = script.new self
|
9
|
+
end
|
10
|
+
@remoting = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name, :scripts
|
14
|
+
|
15
|
+
def run_script script_name
|
16
|
+
scripts[script_name].titled_run
|
17
|
+
close_remoting
|
18
|
+
end
|
19
|
+
|
20
|
+
def instance
|
21
|
+
@instance ||= EC2::Instance.group_running[id_group].try(:first)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ssh_string
|
25
|
+
return '' unless instance
|
26
|
+
"ssh -i #{Settings.aws.keypair_file} -q -o StrictHostKeyChecking=no #{params.ec2.ssh_user}@#{instance.external_ip}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def params
|
30
|
+
Settings.nodes[@name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def remoting
|
34
|
+
@remoting ||= Remoting.new self
|
35
|
+
end
|
36
|
+
|
37
|
+
def id_group
|
38
|
+
"standup_node_#{@name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def close_remoting
|
44
|
+
@remoting.close if @remoting
|
45
|
+
@remoting = nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Standup
|
2
|
+
class Remoting
|
3
|
+
def initialize node
|
4
|
+
@node = node
|
5
|
+
@host = @node.instance.external_ip
|
6
|
+
@keypair_file = Settings.aws.keypair_file
|
7
|
+
@user = @node.params.ec2.ssh_user
|
8
|
+
@ssh = nil
|
9
|
+
@path = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def download *files
|
13
|
+
options = files.pop
|
14
|
+
rsync wrap_to_remote(files), options[:to], options[:sudo]
|
15
|
+
end
|
16
|
+
|
17
|
+
def upload *files
|
18
|
+
options = files.pop
|
19
|
+
rsync files, wrap_to_remote(options[:to]), options[:sudo]
|
20
|
+
end
|
21
|
+
|
22
|
+
def remote_update file, body, opts = {}
|
23
|
+
tmpfile = Tempfile.new('file')
|
24
|
+
|
25
|
+
download file,
|
26
|
+
:to => tmpfile.path,
|
27
|
+
:sudo => opts[:sudo]
|
28
|
+
|
29
|
+
opts[:delimiter] ||= '# standup remote_update fragment'
|
30
|
+
|
31
|
+
initial = File.read(tmpfile.path)
|
32
|
+
|
33
|
+
if initial.empty?
|
34
|
+
bright_p "error during file upload. skipping", Highline::RED
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
to_change = "#{opts[:delimiter]}\n#{body}#{opts[:delimiter]}\n"
|
39
|
+
changed = initial.gsub /#{opts[:delimiter]}.*#{opts[:delimiter]}\n?/m, to_change
|
40
|
+
|
41
|
+
File.open(tmpfile.path, 'w') {|f| f.write changed}
|
42
|
+
|
43
|
+
upload tmpfile.path,
|
44
|
+
:to => file,
|
45
|
+
:sudo => opts[:sudo]
|
46
|
+
end
|
47
|
+
|
48
|
+
def in_dir path
|
49
|
+
raise ArgumentError, 'Only absolute paths allowed' unless path[0,1] == '/'
|
50
|
+
old_path = @path
|
51
|
+
@path = path
|
52
|
+
result = yield
|
53
|
+
@path = old_path
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def exec command
|
58
|
+
command = @path ? "cd #{@path} && #{command}" : command
|
59
|
+
bright_p command
|
60
|
+
ssh.exec! command do |ch, _, data|
|
61
|
+
ch[:result] ||= ""
|
62
|
+
ch[:result] << data
|
63
|
+
print data
|
64
|
+
STDOUT.flush
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def sudo command
|
69
|
+
exec "sudo #{command}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def in_temp_dir &block
|
73
|
+
tmp_dirname = "/tmp/standup_tmp_#{rand 10000}"
|
74
|
+
exec "mkdir #{tmp_dirname}"
|
75
|
+
result = in_dir tmp_dirname, &block
|
76
|
+
exec "rm -rf #{tmp_dirname}"
|
77
|
+
result
|
78
|
+
end
|
79
|
+
|
80
|
+
def file_exists? path
|
81
|
+
exec("if [ -e #{path} ]; then echo 'true'; fi") == "true\n"
|
82
|
+
end
|
83
|
+
|
84
|
+
def install_packages *packages
|
85
|
+
packages = [*packages].flatten.join(' ')
|
86
|
+
sudo "apt-get -qqy install #{packages}"
|
87
|
+
end
|
88
|
+
alias :install_package :install_packages
|
89
|
+
|
90
|
+
def install_gem name, version = nil
|
91
|
+
if version
|
92
|
+
unless exec("gem list | grep #{name}").try(:[], version)
|
93
|
+
sudo "gem install #{name} -v #{version} --no-ri --no-rdoc"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
sudo "gem install #{name} --no-ri --no-rdoc"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def close
|
101
|
+
@ssh.close if @ssh
|
102
|
+
@ssh = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def ssh
|
108
|
+
@ssh ||= Net::SSH.start @host, @user,
|
109
|
+
:keys => @keypair_file,
|
110
|
+
:paranoid => false,
|
111
|
+
:timeout => 10
|
112
|
+
end
|
113
|
+
|
114
|
+
def rsync source, destination, sudo
|
115
|
+
command = [
|
116
|
+
'rsync -azP --delete',
|
117
|
+
"-e 'ssh -i #{@keypair_file} -q -o StrictHostKeyChecking=no'",
|
118
|
+
("--rsync-path='sudo rsync'" if sudo),
|
119
|
+
[*source].join(' '),
|
120
|
+
destination
|
121
|
+
].compact.join(' ')
|
122
|
+
|
123
|
+
3.times do
|
124
|
+
bright_p command
|
125
|
+
break if system command
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def wrap_to_remote files
|
130
|
+
[*files].map{|f| "#{@user}@#{@host}:#{f}"}.join(' ')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_support/hash_with_indifferent_access'
|
2
|
+
|
3
|
+
module Standup
|
4
|
+
module Scripts
|
5
|
+
class Base
|
6
|
+
def initialize node
|
7
|
+
@node = node
|
8
|
+
@remoting = nil
|
9
|
+
@params = if node.params[name].is_a? Hash
|
10
|
+
ActiveSupport::HashWithIndifferentAccess.new self.class.default_params.merge(node.params[name])
|
11
|
+
else
|
12
|
+
node.params[name] || self.class.default_params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class_attribute :name
|
17
|
+
|
18
|
+
class_attribute :default_params
|
19
|
+
self.default_params = {}
|
20
|
+
|
21
|
+
class_attribute :description
|
22
|
+
|
23
|
+
delegate :instance, :open_port, :open_ports, :remoting, :scripts,
|
24
|
+
:to => :@node
|
25
|
+
|
26
|
+
delegate :download, :upload, :remote_update, :exec, :sudo, :in_dir, :in_temp_dir, :file_exists?, :install_package, :install_packages, :install_gem,
|
27
|
+
:to => :remoting
|
28
|
+
|
29
|
+
attr_accessor :node, :params
|
30
|
+
|
31
|
+
def name
|
32
|
+
self.class.name
|
33
|
+
end
|
34
|
+
|
35
|
+
def titled_run
|
36
|
+
bright_p "#{@node.name}:#{name}", HighLine::CYAN
|
37
|
+
run
|
38
|
+
end
|
39
|
+
|
40
|
+
def script_file filename
|
41
|
+
[Standup.local_scripts_path, Standup.gem_scripts_path].each do |dir|
|
42
|
+
next unless dir
|
43
|
+
path = File.expand_path("#{name}/#{filename}", dir)
|
44
|
+
return path if File.exists? path
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def run; end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Standup
|
2
|
+
begin
|
3
|
+
class Settings < Settingslogic
|
4
|
+
source 'config/standup.yml'
|
5
|
+
load!
|
6
|
+
|
7
|
+
aws['account_id'].gsub!(/\D/, '')
|
8
|
+
# keypair_file default to ~/.ssh/keypair_name.pem
|
9
|
+
aws['keypair_file'] ||= "#{File.expand_path '~'}/.ssh/#{aws.keypair_name}.pem"
|
10
|
+
end
|
11
|
+
rescue
|
12
|
+
require 'active_support/hash_with_indifferent_access'
|
13
|
+
remove_const :Settings
|
14
|
+
const_set :Settings, ActiveSupport::HashWithIndifferentAccess.new('nodes' => {})
|
15
|
+
end
|
16
|
+
end
|