breeze 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +4 -0
- data/Rakefile +2 -0
- data/bin/breeze +17 -0
- data/breeze.gemspec +28 -0
- data/lib/breeze.rb +6 -0
- data/lib/breeze/fog_extensions.rb +2 -0
- data/lib/breeze/fog_extensions/aws.rb +65 -0
- data/lib/breeze/initializer.rb +40 -0
- data/lib/breeze/tasks.rb +10 -0
- data/lib/breeze/tasks/app.rb +145 -0
- data/lib/breeze/tasks/configuration.rb +63 -0
- data/lib/breeze/tasks/db.rb +31 -0
- data/lib/breeze/tasks/dns.rb +67 -0
- data/lib/breeze/tasks/list.rb +74 -0
- data/lib/breeze/tasks/server.rb +72 -0
- data/lib/breeze/tasks/server/address.rb +27 -0
- data/lib/breeze/tasks/server/image.rb +27 -0
- data/lib/breeze/tasks/server/tag.rb +16 -0
- data/lib/breeze/veur.rb +86 -0
- data/lib/templates/Thorfile +122 -0
- data/lib/templates/maintenance.html +23 -0
- data/lib/templates/profiles/minimal/scripts/install_conf.sh +4 -0
- data/lib/templates/profiles/minimal/scripts/install_cust.sh +7 -0
- data/lib/templates/profiles/rails_and_image_magick/configs/database.yml +18 -0
- data/lib/templates/profiles/rails_and_image_magick/configs/memcached.conf +51 -0
- data/lib/templates/profiles/rails_and_image_magick/configs/nginx/logrotate +26 -0
- data/lib/templates/profiles/rails_and_image_magick/configs/nginx/monit +9 -0
- data/lib/templates/profiles/rails_and_image_magick/configs/nginx/nginx.conf +99 -0
- data/lib/templates/profiles/rails_and_image_magick/scripts/install_conf.sh +33 -0
- data/lib/templates/profiles/rails_and_image_magick/scripts/install_cust.sh +24 -0
- data/lib/templates/shared/configs/crontab +10 -0
- data/lib/templates/shared/configs/monitrc +248 -0
- data/lib/templates/shared/scripts/credentials.sh +5 -0
- data/lib/templates/shared/scripts/deploy.sh +43 -0
- data/lib/templates/shared/scripts/install.sh +38 -0
- data/lib/templates/user_data.sh +4 -0
- metadata +133 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Markus Bengts
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/breeze
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Breeze
|
6
|
+
class Runner < Thor
|
7
|
+
|
8
|
+
desc 'init [SERVER_TYPE]', 'Initialize a new project with templates for scripts and configuration files'
|
9
|
+
def init(*args)
|
10
|
+
require 'breeze/initializer'
|
11
|
+
invoke('breeze:initializer:init')
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Breeze::Runner.start
|
data/breeze.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "breeze"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "breeze"
|
7
|
+
s.version = Breeze::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Markus Bengts"]
|
10
|
+
s.email = ["markus.bengts@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/markus/breeze"
|
12
|
+
s.summary = %q{Thor tasks to manage AWS cloud computing resorces and deployments}
|
13
|
+
s.description = <<-END_DESCRIPTION
|
14
|
+
Breeze makes it easy to automate server installation and configuration. It provides
|
15
|
+
example scripts and configuration files that you can modify and keep in your revision
|
16
|
+
control system. Thor tasks are provided to create server images, launch server instances etc.
|
17
|
+
END_DESCRIPTION
|
18
|
+
|
19
|
+
s.rubyforge_project = "breeze"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
|
26
|
+
s.add_dependency('thor')
|
27
|
+
s.add_dependency('fog', '>= 0.7')
|
28
|
+
end
|
data/lib/breeze.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'fog/compute/models/aws/server'
|
2
|
+
require 'fog/compute/models/aws/image'
|
3
|
+
|
4
|
+
module Fog
|
5
|
+
|
6
|
+
module AWS
|
7
|
+
class Compute::Server
|
8
|
+
|
9
|
+
def name
|
10
|
+
breeze_data['name'] || tags['Name']
|
11
|
+
end
|
12
|
+
|
13
|
+
def display_name
|
14
|
+
name || public_ip_address || "#{state} #{flavor_id} #{id}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def running? ; current_state == 'running' ; end
|
18
|
+
def stopped? ; current_state == 'stopped' ; end
|
19
|
+
|
20
|
+
# Get or set meta data that is saved in a tag.
|
21
|
+
def breeze_data(new_values=nil)
|
22
|
+
if new_values
|
23
|
+
tags['breeze-data'] = new_values.map{ |k,v| v.nil? ? v : "#{k}:#{v}" }.compact.join(';')
|
24
|
+
# thor("server:tag:create #{id} breeze-data '#{tags['breeze-data']}'")
|
25
|
+
Breeze::Server::Tag.new.create(id, 'breeze-data', tags['breeze-data'])
|
26
|
+
else
|
27
|
+
Hash[tags['breeze-data'].to_s.split(';').map{ |s| s.split(':') }]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def spare_for_rollback!
|
32
|
+
breeze_state('spare_for_rollback')
|
33
|
+
end
|
34
|
+
|
35
|
+
def spare_for_rollback?
|
36
|
+
breeze_state == 'spare_for_rollback'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get or set the state tag.
|
40
|
+
def breeze_state(new_state=nil)
|
41
|
+
if new_state
|
42
|
+
breeze_data(breeze_data.merge('state' => new_state))
|
43
|
+
else
|
44
|
+
breeze_data['state']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def current_state
|
51
|
+
reload
|
52
|
+
state
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
class Compute::Image
|
57
|
+
|
58
|
+
def full_type
|
59
|
+
"#{type}, #{architecture}, #{root_device_type}"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Breeze
|
4
|
+
class Initializer < Thor
|
5
|
+
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
desc 'init [SERVER_PROFILE]', 'described in bin/breeze'
|
9
|
+
def init(profile='rails_and_image_magick')
|
10
|
+
raise(ArgumentError, "*#{profile}* is not supported") unless profile_exists?(profile)
|
11
|
+
copy_file('Thorfile')
|
12
|
+
copy_file('maintenance.html', "#{config_dir}/maintenance.html")
|
13
|
+
copy_file('user_data.sh', "#{config_dir}/user_data.sh")
|
14
|
+
directories('shared', profile_path(profile))
|
15
|
+
chmod_600('Thorfile', "#{config_dir}/scripts/credentials.sh")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def profile_exists?(profile)
|
21
|
+
File.directory?("#{self.class.source_root}/#{profile_path(profile)}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def directories(*dirs)
|
25
|
+
dirs.each{ |dir| directory(dir, config_dir) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def chmod_600(*files)
|
29
|
+
files.each{ |file| chmod(file, 0600) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def config_dir ; "config/breeze" ; end
|
33
|
+
def profile_path(p) ; "profiles/#{p}" ; end
|
34
|
+
|
35
|
+
def self.source_root
|
36
|
+
File.join(File.dirname(__FILE__), '..', 'templates')
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/breeze/tasks.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'breeze'
|
2
|
+
require 'breeze/tasks/configuration'
|
3
|
+
require 'breeze/tasks/list'
|
4
|
+
require 'breeze/tasks/server'
|
5
|
+
require 'breeze/tasks/server/address'
|
6
|
+
require 'breeze/tasks/server/image'
|
7
|
+
require 'breeze/tasks/server/tag'
|
8
|
+
require 'breeze/tasks/dns'
|
9
|
+
require 'breeze/tasks/db'
|
10
|
+
require 'breeze/tasks/app'
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'breeze/tasks/server'
|
2
|
+
|
3
|
+
module Breeze
|
4
|
+
|
5
|
+
class App < Server
|
6
|
+
|
7
|
+
desc 'start PUBLIC_SERVER_NAME [DB_SERVER_NAME] [DB_NAME]', 'Start a new app with web server and db'
|
8
|
+
method_options :db => true, :db_to_clone => :string, :elastic_ip => true, :dns_ttl => 60, :deploy_branch => :string
|
9
|
+
def start(public_server_name, db_server_name=nil, db_name=nil)
|
10
|
+
if options[:db]
|
11
|
+
raise 'DB_SERVER_NAME is required unless --no-db is given.' if db_server_name.nil?
|
12
|
+
if options[:db_to_clone]
|
13
|
+
thor("db:clone #{options[:db_to_clone]} #{db_server_name}")
|
14
|
+
else
|
15
|
+
thor("db:create #{db_server_name} #{db_name}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
server = create_server
|
19
|
+
server.breeze_data(:name => public_server_name, :db => db_server_name)
|
20
|
+
thor("server:address:create #{server.id}") if options[:elastic_ip]
|
21
|
+
thor("dns:record:create #{zone_id(public_server_name)} #{public_server_name}. A #{ip(server)} #{options[:dns_ttl]}")
|
22
|
+
deploy_command([server], public_server_name, db_server_name, options[:deploy_branch]) if options[:deploy_branch]
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'stop PUBLIC_SERVER_NAME', 'Destroy web server and db'
|
26
|
+
method_options :force => false
|
27
|
+
def stop(public_server_name)
|
28
|
+
dbs_to_destroy = []
|
29
|
+
active_servers(public_server_name).each do |server|
|
30
|
+
server.addresses.each do |address|
|
31
|
+
thor("server:address:release #{address.public_ip}")
|
32
|
+
end
|
33
|
+
thor("dns:record:destroy #{zone_id(public_server_name)} #{public_server_name}. A")
|
34
|
+
dbs_to_destroy << server.breeze_data['db']
|
35
|
+
thor("server:destroy #{server.id}")
|
36
|
+
end
|
37
|
+
dbs_to_destroy.uniq.compact.each do |db_name|
|
38
|
+
thor("db:destroy #{db_name}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'disable PUBLIC_SERVER_NAME', 'Upload system/maintenance.html to web servers'
|
43
|
+
def disable(public_server_name)
|
44
|
+
on_each_server(disable_app_command, public_server_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'enable PUBLIC_SERVER_NAME', 'Remove system/maintenance.html from web servers'
|
48
|
+
def enable(public_server_name)
|
49
|
+
on_each_server(enable_app_command, public_server_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'deploy PUBLIC_SERVER_NAME DB_SERVER_NAME BRANCH', 'Deploy a new version by replacing old servers with new ones'
|
53
|
+
def deploy(public_server_name, db_server_name, branch)
|
54
|
+
old_server = active_servers(public_server_name).first
|
55
|
+
new_server = create_server
|
56
|
+
new_server.breeze_data(:name => public_server_name, :db => db_server_name)
|
57
|
+
deploy_command([new_server], public_server_name, db_server_name, branch)
|
58
|
+
puts("The new server should soon be available at #{ip(new_server)}.")
|
59
|
+
if ask("Ready to continue and move the elastic_ip for #{public_server_name} to the new server? [YES/rollback] >") =~ /r|n/i
|
60
|
+
remote("sudo shutdown -h +#{CONFIGURATION[:rollback_window]}", :host => ip(old_server))
|
61
|
+
old_server.spare_for_rollback!
|
62
|
+
move_addresses(old_server, new_server)
|
63
|
+
else
|
64
|
+
new_server.destroy
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'rollback PUBLIC_SERVER_NAME', 'Rollback a deploy'
|
69
|
+
def rollback(public_server_name)
|
70
|
+
old_server = spare_servers(public_server_name).first
|
71
|
+
remote('sudo shutdown -c', :host => ip(old_server))
|
72
|
+
new_server = active_servers(public_server_name).first
|
73
|
+
remote(disable_app_command, :host => ip(new_server))
|
74
|
+
move_addresses(new_server, old_server)
|
75
|
+
old_server.breeze_state('reactivated')
|
76
|
+
new_server.breeze_state('abandoned_due_to_rollback')
|
77
|
+
if accept?('Destroy the abandoned server NOW?')
|
78
|
+
new_server.destroy
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def move_addresses(from_server, to_server)
|
85
|
+
from_server.addresses.each do |address|
|
86
|
+
thor("server:address:associate #{address.public_ip} #{to_server.id}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def servers(public_server_name)
|
91
|
+
fog.servers.select{ |s| s.name == public_server_name }
|
92
|
+
end
|
93
|
+
|
94
|
+
def spare_servers(public_server_name)
|
95
|
+
servers(public_server_name).select{ |s| s.spare_for_rollback? }
|
96
|
+
end
|
97
|
+
|
98
|
+
def active_servers(public_server_name)
|
99
|
+
servers(public_server_name).select{ |s| ! s.spare_for_rollback? }
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_each_server(command, public_server_name)
|
103
|
+
active_servers(public_server_name).each do |server|
|
104
|
+
remote(command, :host => ip(server))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def disable_app_command
|
109
|
+
"cd #{CONFIGURATION[:app_path]} && cp config/breeze/maintenance.html public/system/"
|
110
|
+
end
|
111
|
+
|
112
|
+
def enable_app_command
|
113
|
+
"rm #{CONFIGURATION[:app_path]}/public/system/maintenance.html"
|
114
|
+
end
|
115
|
+
|
116
|
+
def db_endpoint(db_server_name)
|
117
|
+
db = rds.servers.get(db_server_name)
|
118
|
+
return nil unless db
|
119
|
+
unless db.ready?
|
120
|
+
print('Waiting for the db')
|
121
|
+
wait_until { db.reload; db.ready? }
|
122
|
+
end
|
123
|
+
db.endpoint['Address']
|
124
|
+
end
|
125
|
+
|
126
|
+
def ip(server)
|
127
|
+
address = server.addresses.first
|
128
|
+
address ? address.public_ip : server.public_ip_address
|
129
|
+
end
|
130
|
+
|
131
|
+
def zone_id(name)
|
132
|
+
find_zone_recursively(name).id or raise("CANNOT FIND DNS ZONE FOR #{name}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_zone_recursively(name)
|
136
|
+
return nil unless name.include?('.')
|
137
|
+
dns.zones.detect{ |z| z.domain == "#{name}." } or find_zone_recursively(name.sub(/[^.]*\./, ''))
|
138
|
+
end
|
139
|
+
|
140
|
+
def thor(task)
|
141
|
+
super(task + (options[:force] ? ' --force' : ''))
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Breeze
|
6
|
+
|
7
|
+
# The idea was stolen from rubber: https://github.com/wr0ngway/rubber
|
8
|
+
# but this is a simple implementation with no support for roles and additives.
|
9
|
+
# See https://github.com/wr0ngway/rubber/wiki/Configuration
|
10
|
+
class Configuration < Veur
|
11
|
+
|
12
|
+
desc 'deploy_to_localhost',
|
13
|
+
'Transform and deploy server configuration files to the local file system based on ERB templates in config/server'
|
14
|
+
method_option :force, :default => false, :desc => 'Overwrite and execute @post commands even if files would not change'
|
15
|
+
def deploy_to_localhost
|
16
|
+
Dir['config/breeze/configs/**/*'].each do |path|
|
17
|
+
transform_and_deploy(path, options[:force]) unless File.directory?(path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def transform_and_deploy(file, force=false)
|
24
|
+
@read_cmd = @write_cmd = @path = @perms = @owner = @group = @post = nil
|
25
|
+
transformed = ERB.new(File.read(file)).result(binding)
|
26
|
+
original = IO.read(@path||"|#{@read_cmd}") if @path.nil? or File.exist?(@path)
|
27
|
+
report_errors(@read_cmd)
|
28
|
+
if force or original != transformed
|
29
|
+
ensure_directory_and_backup if @path
|
30
|
+
open(@path||"|#{@write_cmd}", 'w') { |f| f.write(transformed) }
|
31
|
+
report_errors(@write_cmd)
|
32
|
+
set_owner_and_permissions if @path
|
33
|
+
if @post
|
34
|
+
system("set -e; #{@post}")
|
35
|
+
report_errors(@post)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def ensure_directory_and_backup
|
41
|
+
if File.exist?(@path)
|
42
|
+
FileUtils.cp(@path, "#{@path}.backup", :preserve => true)
|
43
|
+
else
|
44
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_owner_and_permissions
|
49
|
+
FileUtils.chmod(@perms, @path) if @perms
|
50
|
+
FileUtils.chown(@owner, @group, @path) if @owner || @group
|
51
|
+
end
|
52
|
+
|
53
|
+
def report_errors(cmd)
|
54
|
+
raise "COMMAND FAILED: #{cmd}" unless $?.nil? || $?.success?
|
55
|
+
end
|
56
|
+
|
57
|
+
# a helper for ERB templates
|
58
|
+
def host_name
|
59
|
+
Socket.gethostname
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Breeze
|
2
|
+
|
3
|
+
class Db < Veur
|
4
|
+
|
5
|
+
desc 'create SERVER_NAME [DB_NAME]', 'Create a new database server'
|
6
|
+
method_options CONFIGURATION[:default_db_options]
|
7
|
+
def create(name, db_name=nil)
|
8
|
+
options.update(:id => name, :db_name => db_name)
|
9
|
+
# puts "DB options: #{options}"
|
10
|
+
rds.servers.create(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'destroy NAME', 'Destroy a database server'
|
14
|
+
method_options :force => false
|
15
|
+
def destroy(name)
|
16
|
+
db = rds.servers.get(name)
|
17
|
+
if not %w(available failed storage-full incompatible-parameters incompatible-restore).include?(db.state)
|
18
|
+
puts "ERROR: cannot destroy db while state is #{db.state}!"
|
19
|
+
elsif force_or_accept?("Destroy DB #{name}?")
|
20
|
+
db.destroy(nil)
|
21
|
+
db.reload
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'clone OLD_DB NEW_DB', 'Create a new db server using the latest backup of OLD_DB.'
|
26
|
+
def clone(old_db, new_db)
|
27
|
+
rds.restore_db_instance_to_point_in_time(old_db, new_db, 'UseLatestRestorableTime' => true)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|