breeze 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +4 -0
  5. data/Rakefile +2 -0
  6. data/bin/breeze +17 -0
  7. data/breeze.gemspec +28 -0
  8. data/lib/breeze.rb +6 -0
  9. data/lib/breeze/fog_extensions.rb +2 -0
  10. data/lib/breeze/fog_extensions/aws.rb +65 -0
  11. data/lib/breeze/initializer.rb +40 -0
  12. data/lib/breeze/tasks.rb +10 -0
  13. data/lib/breeze/tasks/app.rb +145 -0
  14. data/lib/breeze/tasks/configuration.rb +63 -0
  15. data/lib/breeze/tasks/db.rb +31 -0
  16. data/lib/breeze/tasks/dns.rb +67 -0
  17. data/lib/breeze/tasks/list.rb +74 -0
  18. data/lib/breeze/tasks/server.rb +72 -0
  19. data/lib/breeze/tasks/server/address.rb +27 -0
  20. data/lib/breeze/tasks/server/image.rb +27 -0
  21. data/lib/breeze/tasks/server/tag.rb +16 -0
  22. data/lib/breeze/veur.rb +86 -0
  23. data/lib/templates/Thorfile +122 -0
  24. data/lib/templates/maintenance.html +23 -0
  25. data/lib/templates/profiles/minimal/scripts/install_conf.sh +4 -0
  26. data/lib/templates/profiles/minimal/scripts/install_cust.sh +7 -0
  27. data/lib/templates/profiles/rails_and_image_magick/configs/database.yml +18 -0
  28. data/lib/templates/profiles/rails_and_image_magick/configs/memcached.conf +51 -0
  29. data/lib/templates/profiles/rails_and_image_magick/configs/nginx/logrotate +26 -0
  30. data/lib/templates/profiles/rails_and_image_magick/configs/nginx/monit +9 -0
  31. data/lib/templates/profiles/rails_and_image_magick/configs/nginx/nginx.conf +99 -0
  32. data/lib/templates/profiles/rails_and_image_magick/scripts/install_conf.sh +33 -0
  33. data/lib/templates/profiles/rails_and_image_magick/scripts/install_cust.sh +24 -0
  34. data/lib/templates/shared/configs/crontab +10 -0
  35. data/lib/templates/shared/configs/monitrc +248 -0
  36. data/lib/templates/shared/scripts/credentials.sh +5 -0
  37. data/lib/templates/shared/scripts/deploy.sh +43 -0
  38. data/lib/templates/shared/scripts/install.sh +38 -0
  39. data/lib/templates/user_data.sh +4 -0
  40. metadata +133 -0
@@ -0,0 +1,6 @@
1
+ config/*
2
+ pkg/*
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ /Thorfile
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in breeze.gemspec
4
+ gemspec
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.
@@ -0,0 +1,4 @@
1
+ # breeze
2
+
3
+ Breeze provides some [Thor](https://github.com/wycats/thor) tasks and example scripts for managing cloud computing resources
4
+ on [Amazon's AWS cloud](http://aws.amazon.com/).
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ module Breeze
2
+
3
+ VERSION = "0.0.1"
4
+ autoload :Veur, 'breeze/veur'
5
+
6
+ end
@@ -0,0 +1,2 @@
1
+ require 'fog'
2
+ require 'breeze/fog_extensions/aws'
@@ -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
@@ -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