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.
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