breeze 0.0.1
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 +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
|