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,67 @@
1
+ module Breeze
2
+ module Dns
3
+
4
+ class Zone < Veur
5
+
6
+ desc 'create DOMAIN', 'Create a new DNS zone'
7
+ def create(domain)
8
+ zone = dns.zones.create(:domain => domain)
9
+ puts "Zone ID: #{zone.id}"
10
+ puts "Change info: #{zone.change_info}"
11
+ puts "Name servers: #{zone.nameservers}"
12
+ end
13
+
14
+ desc 'destroy ZONE_ID', 'Destroy a DNS zone'
15
+ def destroy(zone_id)
16
+ zone = dns.zones.get(zone_id)
17
+ if accept?("Destroy DNS zone and records for #{zone.domain}?")
18
+ zone.records.each(&:destroy)
19
+ zone.destroy
20
+ end
21
+ end
22
+
23
+ desc 'import ZONE_ID FILE', 'Creates dns records specified in FILE'
24
+ long_desc <<-END_DESC
25
+ FILE should be the path to a ruby file that defines DNS_RECORDS like this:
26
+ DNS_RECORDS = [
27
+ {:name => 'example.com', :type => 'A', :ip => '1.2.3.4'},
28
+ {:name => 'www.example.com', :type => 'CNAME', :ip => 'example.com'}
29
+ ]
30
+ You can also specify :ttl for each record. The default ttl is 3600 (1 hour).
31
+ END_DESC
32
+ def import(zone_id, file)
33
+ load file
34
+ zone = get_zone(zone_id)
35
+ DNS_RECORDS.each do |record_hash|
36
+ zone.records.create(record_hash)
37
+ puts record_hash.inspect
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def get_zone(id)
44
+ dns.zones.get(id)
45
+ end
46
+
47
+ end
48
+
49
+ class Record < Zone
50
+
51
+ desc 'create ZONE_ID NAME TYPE IP [TTL]', 'Create a new DNS record'
52
+ def create(zone_id, name, type, ip, ttl=3600)
53
+ get_zone(zone_id).records.create(:name => name, :type => type, :ip => ip, :ttl => ttl)
54
+ end
55
+
56
+ desc 'destroy ZONE_ID NAME [TYPE]', 'Destroy a DNS record'
57
+ method_options :force => false
58
+ def destroy(zone_id, name, type=nil)
59
+ records = get_zone(zone_id).records.select{ |r| r.name == name && (type.nil? || r.type == type) }
60
+ if force_or_accept?("Destroy #{records.size} record#{records.size == 1 ? '' : 's'}?")
61
+ records.each(&:destroy)
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ module Breeze
2
+
3
+ class List < Veur
4
+
5
+ desc :cloud_resources, 'List all cloud resources that the current account can control with breeze'
6
+ def cloud_resources
7
+ images
8
+ servers
9
+ addresses
10
+ volumes
11
+ db_servers
12
+ dns_zones
13
+ end
14
+
15
+ desc :images, 'Describe machine images owned by Breeze::CONFIGURATION[:image_owner]'
16
+ def images
17
+ report 'MACHINE IMAGES',
18
+ ['Name or Location', 'Image ID', 'Owner', 'Image Type', 'Public'],
19
+ fog.images.all('Owner' => Breeze::CONFIGURATION[:image_owner]).map{ |i|
20
+ [i.name||i.location, i.id, i.owner_id, i.full_type, i.is_public]
21
+ }
22
+ end
23
+
24
+ desc :servers, 'Describe server instances'
25
+ def servers
26
+ report "SERVER INSTANCES",
27
+ ['Name', 'Instance ID', 'IP Address', 'Image ID', 'Instance Type', 'Availability Zone', 'State'],
28
+ fog.servers.map { |i|
29
+ [i.name, i.id, i.public_ip_address, i.image_id, i.flavor_id, i.availability_zone, i.state]
30
+ }
31
+ end
32
+
33
+ desc :addresses, 'List allocated ip addresses'
34
+ def addresses
35
+ report "ALLOCATED IP ADDRESSES",
36
+ ['Address', 'Server'],
37
+ fog.addresses.map{ |a| [a.public_ip, a.server_id] }
38
+ end
39
+
40
+ desc :volumes, 'Describe block store volumes (EBS)'
41
+ def volumes
42
+ report "VOLUMES",
43
+ ['Volume ID', 'Size', 'Status', 'Zone', 'Snapshot ID', 'Used by'],
44
+ fog.volumes.map { |v|
45
+ [v.id, v.size, v.state, v.availability_zone, v.snapshot_id, v.server_id]
46
+ }
47
+ end
48
+
49
+ desc :db_servers, 'List database servers'
50
+ def db_servers
51
+ report "DATABASE SERVERS",
52
+ ['Name', 'Type', 'Storage', 'State', 'Endpoint'],
53
+ rds.servers.map{ |s| [s.id, s.flavor_id, s.allocated_storage, s.state, s.endpoint] }
54
+ end
55
+
56
+ desc :dns_zones, 'Describe DNS zones'
57
+ def dns_zones
58
+ zones = dns.zones
59
+ zones.each(&:reload) # necessary in order to get nameservers
60
+ report "DNS ZONES",
61
+ ['Domain', 'Zone ID', 'Name servers'],
62
+ zones.map{ |z| [z.domain, z.id, z.nameservers.join(', ')] }
63
+ end
64
+
65
+ desc 'dns_records ZONE_ID', 'List all DNS records for the given zone'
66
+ def dns_records(zone_id)
67
+ zone = dns.zones.get(zone_id)
68
+ report "DNS RECORDS FOR #{zone.domain}",
69
+ ['Name', 'Type', 'TTL', 'Value'],
70
+ zone.records.map{ |r| [r.name, r.type, r.ttl, r.ip.join(', ')] }
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,72 @@
1
+ require 'resolv'
2
+
3
+ module Breeze
4
+
5
+ # Dealing with server instances.
6
+ class Server < Veur
7
+
8
+ desc 'create', 'Launch a new server instance'
9
+ method_options CONFIGURATION[:default_server_options]
10
+ def create
11
+ if options[:user_data_file]
12
+ options[:user_data] = Base64.encode64(File.read(options[:user_data_file])).strip
13
+ end
14
+ create_server(options)
15
+ end
16
+
17
+ desc 'destroy INSTANCE_ID', 'Terminate a running (or stopped) server instance'
18
+ method_options :force => false
19
+ def destroy(instance_id)
20
+ server = fog.servers.get(instance_id)
21
+ if force_or_accept?("Terminate server #{server.display_name}?")
22
+ server.destroy
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def create_server(options=nil)
29
+ options ||= CONFIGURATION[:default_server_options]
30
+ # puts("Server options: #{options.inspect}")
31
+ server = fog.servers.create(options)
32
+ print "Launching server #{server.id}"
33
+ wait_until('running!') { server.running? }
34
+ return server
35
+ end
36
+
37
+ def wait_until_host_is_available(host)
38
+ if Resolv.getaddresses(host).empty?
39
+ print("Waiting for #{host} to resolve")
40
+ wait_until('ready!') { Resolv.getaddresses(host).any? }
41
+ end
42
+ return true if remote_is_available?(host)
43
+ print("Waiting for #{host} to accept connections")
44
+ wait_until('ready!') { remote_is_available?(host) }
45
+ end
46
+
47
+ def remote_is_available?(host)
48
+ execute(:remote_command, :command => 'exit', :host => host)
49
+ end
50
+
51
+ def remote(command, args)
52
+ args[:command] = command
53
+ execute(:remote_command, args)
54
+ end
55
+
56
+ def upload(file_pattern, args)
57
+ args[:file_pattern] = file_pattern
58
+ execute(:upload_command, args)
59
+ end
60
+
61
+ def execute(command, args)
62
+ command = CONFIGURATION[command] if command.is_a?(Symbol)
63
+ # system(command % args)
64
+ # rescue ArgumentError # for ruby 1.8 compatibility
65
+ args.each do |key, value|
66
+ command = command.gsub("%{#{key}}", value)
67
+ end
68
+ system(command)
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,27 @@
1
+ module Breeze
2
+
3
+ # Aka Amazon Elastic IP
4
+ class Server::Address < Veur
5
+
6
+ desc 'create SERVER_ID', 'Create and associate a new elastic ip'
7
+ def create(server_id)
8
+ # TODO: fog should take server_id directly when creating an address
9
+ server = fog.servers.get(server_id)
10
+ fog.addresses.create(:server => server)
11
+ end
12
+
13
+ desc 'release IP', 'Release the ip address'
14
+ method_options :force => false
15
+ def release(ip)
16
+ if force_or_accept?("Release IP #{ip}?")
17
+ fog.addresses.get(ip).destroy
18
+ end
19
+ end
20
+
21
+ desc 'associate IP NEW_SERVER_ID', 'Associate an existing IP with a new server'
22
+ def associate(ip, server_id)
23
+ fog.associate_address(server_id, ip)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'breeze/tasks/server'
2
+
3
+ module Breeze
4
+
5
+ # Dealing with machine images.
6
+ class Server::Image < Server
7
+ desc 'create', 'Launch a server with the base image, wait for it to boot, invoke prepare_private_image(ip_address), ' +
8
+ 'stop the server, create a new machine image as a snapshot of the root device, and destroy the server.'
9
+ method_options CONFIGURATION[:default_server_options].merge(CONFIGURATION[:create_image_options])
10
+ def create
11
+ options[:block_device_mapping] = [{:device_name => '/dev/sda1', :ebs_volume_size => options.delete(:root_device_size)}]
12
+ server = create_server(options)
13
+ prepare_private_image(server.public_ip_address)
14
+ print('Stopping the server before saving a snapshot')
15
+ server.stop
16
+ wait_until('stopped!') { server.stopped? }
17
+ thor('list:images')
18
+ image = fog.images.create(:name => ask('Image name >'), :instance_id => server.id)
19
+ server.destroy
20
+ puts
21
+ puts("===> Created image #{image.id} and terminated temporary server #{server.id}.")
22
+ puts
23
+ puts("NOTICE: it may take a while before the new image shows up in list:images")
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Breeze
2
+
3
+ class Server::Tag < Veur
4
+
5
+ desc 'create SERVER_ID KEY VALUE', 'Create or update a tag'
6
+ def create(server_id, key, value)
7
+ fog.tags.create(:resource_id => server_id, :key => key, :value => value)
8
+ end
9
+
10
+ desc 'destroy SERVER_ID KEY', 'Delete a tag'
11
+ def destroy(server_id, key)
12
+ fog.tags.get(key).detect{ |tag| tag.resource_id == server_id }.destroy
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,86 @@
1
+ require 'breeze/fog_extensions'
2
+
3
+ module Breeze
4
+
5
+ # Thor is also known as Veur. Veur means "guard of the shrine"
6
+ # (possibly) according to Wikipedia.
7
+ class Veur < Thor
8
+
9
+ include Thor::Actions
10
+
11
+ # shorten the task names
12
+ def self.inherited(c)
13
+ c.class_eval do
14
+ namespace Thor::Util.namespace_from_thor_class(c).sub('breeze:', '')
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ # Thor freezes the options (don't understand why)
21
+ def options
22
+ @unfrozen_options ||= super.dup
23
+ end
24
+
25
+ # yes? in thor cannot be accepted with enter
26
+ def accept?(question)
27
+ ! (ask("#{question} [YES/no] >") =~ /n/i)
28
+ end
29
+
30
+ # don't ask questions if given the --force option
31
+ def force_or_accept?(question)
32
+ options[:force] or accept?(question)
33
+ end
34
+
35
+ # Print out dots while waiting for something.
36
+ # Usage:
37
+ # print "My task is running..."
38
+ # wait_until { my_task.completed? }
39
+ def wait_until(message='completed!')
40
+ 3.times { dot_and_sleep(1) }
41
+ dot_and_sleep(2) until yield
42
+ puts message
43
+ end
44
+
45
+ # a helper for wait_until
46
+ def dot_and_sleep(interval)
47
+ print('.')
48
+ sleep(interval)
49
+ end
50
+
51
+ # Print a table with a title and a top border of matching width.
52
+ def report(title, columns, rows)
53
+ table = capture_table([columns] + rows)
54
+ title = "=== #{title} "
55
+ title << "=" * [(table.lines.max{|s| s.size }.size - title.size), 3].max
56
+ puts title
57
+ puts table
58
+ end
59
+
60
+ # capture table in order to determine it's width
61
+ def capture_table(table)
62
+ return 'none' if table.size == 1 # the first row is for column titles
63
+ $stdout = StringIO.new # start capturing the output
64
+ print_table(table.map{ |row| row.map(&:to_s) })
65
+ output = $stdout
66
+ $stdout = STDOUT # restore normal output
67
+ return output.string
68
+ end
69
+
70
+ def fog
71
+ @fog ||= Fog::Compute.new(CONFIGURATION[:cloud_service])
72
+ end
73
+
74
+ def dns
75
+ @dns ||= Fog::DNS.new(CONFIGURATION[:cloud_service])
76
+ end
77
+
78
+ def rds
79
+ return @rds if @rds
80
+ credentials = CONFIGURATION[:cloud_service].reject{ |k,v| k == :provider }
81
+ credentials[:region] = CONFIGURATION[:db_region]
82
+ @rds = Fog::AWS::RDS.new(credentials)
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,122 @@
1
+ # Sign up for AWS credentials at http://aws.amazon.com/, and learn how to
2
+ # upload your own public ssh key at http://alestic.com/2010/10/ec2-ssh-keys.
3
+ # Use environment variables in this file if you share it with others.
4
+
5
+ require 'breeze'
6
+
7
+ Breeze::CONFIGURATION = {
8
+
9
+ # Access credentials are needed for all tasks.
10
+ :cloud_service => {
11
+ :provider => 'AWS',
12
+ :aws_access_key_id => 'YOUR-ACCESS-KEY-ID',
13
+ :aws_secret_access_key => 'YOUR-SECTET-ACCESS-KEY'
14
+ },
15
+
16
+ # Remote and upload commands are required in order to create a server image or deploy
17
+ # an application. Arguments marked with %{} are automatically replaced when the
18
+ # commands are used. Use "ssh -i /path/to/key" if not using your default ssh key.
19
+ :remote_command => "ssh ubuntu@%{host} '%{command}'",
20
+ :upload_command => 'rsync -v %{file_pattern} ubuntu@%{host}:%{remote_path}',
21
+
22
+ # :rollback_window specifies the number of minutes to keep old instances running after new ones
23
+ # have been deployed. Rollback is no longer possible when the old instances have been destroyed.
24
+ :rollback_window => 60,
25
+
26
+ # Default server options are needed when launching new servers.
27
+ :default_server_options => {
28
+ :image_id => 'YOUR-PRIVATE-AMI-OR-A-PUBLIC-ONE',
29
+ :key_name => 'THE-NAME-OF-YOUR-KEYPAIR', # http://alestic.com/2010/10/ec2-ssh-keys
30
+ :flavor_id => 't1.micro', # t1.micro m1.small c1.medium ...
31
+ :availability_zone => 'us-east-1a', # us-west-1a eu-west-1a ap-southeast-1a
32
+ :user_data_file => nil
33
+ },
34
+
35
+ # Override default_server_options when building your own private server image:
36
+ :create_image_options => {
37
+ :image_id => 'ami-ccf405a5', # a base AMI from http://alestic.com
38
+ :root_device_size => 15 # in GB
39
+ },
40
+
41
+ # machine images owned by this account are included in list:images (provided that you have access to them)
42
+ :image_owner => 'YOUR-ACCOUNT-ID-WITHOUT-DASHES', # canonical: '099720109477'
43
+
44
+ # db configuration is required in order to use Amazon RDS
45
+ :db_region => 'us-east-1', # us-west-1 eu-west-1 ap-southeast-1
46
+ :default_db_options => {
47
+ :engine => 'mysql',
48
+ :engine_version => '5.5',
49
+ :auto_minor_version_upgrade => true,
50
+ :allocated_storage => 5, # 5 - 1024 GB
51
+ :availability_zone => 'us-east-1a',
52
+ :backup_retention_period => 1, # 0 - 8 days
53
+ :preferred_backup_window => '05:30-06:00', # daily, times in UTC
54
+ :preferred_maintenance_window => 'sun:06:00-sun:06:30', # weekly
55
+ :master_username => 'admin',
56
+ :password => 'admin'
57
+ },
58
+
59
+ # you can add your own keys and access this hash as CONFIGURATION in the erb templates
60
+ :admin_email => 'YOUR-EMAIL',
61
+ :app_path => '/srv/YOUR-APP'
62
+ }.freeze
63
+
64
+ # Define Breeze::CONFIGURATION before requiring the tasks.
65
+ # This allows us to pick up default options that can be viewed
66
+ # with the "thor help" command.
67
+ require 'breeze/tasks'
68
+
69
+ class Breeze::Server::Image
70
+ private
71
+ # This method is called from server:image:create. Modify this and/or the scripts that
72
+ # are uploaded in order to create your own private machine image.
73
+ def prepare_private_image(ip_address)
74
+ wait_until_host_is_available(ip_address)
75
+ puts("Uploading scripts...")
76
+ upload('config/breeze/scripts/*.sh', :host => ip_address, :remote_path => './')
77
+ remote('chmod 600 credentials.sh; chmod 700 deploy.sh; bash install.sh', :host => ip_address)
78
+ end
79
+ end
80
+
81
+ # You can define staging:start etc. with fixed parameters below.
82
+
83
+ class Breeze::App
84
+ private
85
+ def deploy_command(servers, public_server_name, db_server_name, branch)
86
+ servers.each do |server|
87
+ wait_until_host_is_available(ip(server))
88
+ remote("/home/ubuntu/deploy.sh #{public_server_name} #{db_endpoint(db_server_name)} #{branch}", :host => ip(server))
89
+ end
90
+ end
91
+ end
92
+
93
+ # thor staging:start and staging:stop
94
+ class Staging < Breeze::App
95
+
96
+ PUBLIC_SERVER_NAME = 'staging.example.com'
97
+ DB_SERVER_NAME = 'staging-db'
98
+
99
+ desc 'start', 'Start web server and db for staging'
100
+ def start
101
+ thor("app:start #{PUBLIC_SERVER_NAME} #{DB_SERVER_NAME} --db-to-clone=#{Production::DB_SERVER_NAME} --deploy-branch=master")
102
+ end
103
+
104
+ desc 'stop', 'Stop staging and destroy server and db'
105
+ def stop
106
+ thor("app:stop #{PUBLIC_SERVER_NAME} --force")
107
+ end
108
+
109
+ end
110
+
111
+ # thor production:start
112
+ class Production < Breeze::App
113
+
114
+ PUBLIC_SERVER_NAME = 'production.example.com'
115
+ DB_SERVER_NAME = 'production-db'
116
+
117
+ desc 'start', 'Start web server and db for production'
118
+ def start
119
+ thor("app:start #{PUBLIC_SERVER_NAME} #{DB_SERVER_NAME} crags --dns-ttl=300 --deploy-branch=stable")
120
+ end
121
+
122
+ end