cloudboot 0.0.0

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jan Zimmek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = cloudboot
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2009 Jan Zimmek. See LICENSE for details.
data/bin/cloudboot ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'cloudboot'
5
+ require 'yaml'
6
+
7
+ options = {}
8
+ options[:config] = "default"
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: cloudboot --help"
12
+
13
+ opts.on("--generate-config [CONFIG]", "Generate a default configuration file") do |value|
14
+ options[:action] = :generate_config
15
+ options[:config] = value if value
16
+ end
17
+
18
+ opts.on("--config CONFIG", "Use this configuration file") do |value|
19
+ options[:config] = value
20
+ end
21
+
22
+ opts.on("--cronjob", "Generates a sample cronjob") do
23
+ options[:action] = :cronjob
24
+ end
25
+
26
+ opts.on("--list-instances", "Show a list of instances") do
27
+ options[:action] = :list_instances
28
+ end
29
+
30
+ opts.on("-s", "--server", "Generates a server skeleton") do |name|
31
+ options[:action] = :server
32
+ end
33
+
34
+ opts.on("-c", "--client", "Generates a client skeleton") do |name|
35
+ options[:action] = :client
36
+ end
37
+
38
+ opts.on("--userdata", "Generates a userdata example") do |name|
39
+ options[:action] = :userdata
40
+ end
41
+
42
+ end
43
+
44
+ opts.parse!
45
+
46
+ module Cloudboot
47
+ class Bin
48
+
49
+ def initialize(options)
50
+ @options = options
51
+ end
52
+
53
+ def action
54
+ @options[:action]
55
+ end
56
+
57
+ def config
58
+ YAML.load_file(config_file)
59
+ end
60
+
61
+ def run
62
+
63
+ raise "call: cloudboot --generate-config #{@options[:config]}" if ![:generate_config, :client].include?(action) && !config_exist?
64
+
65
+ case action
66
+
67
+ when :generate_config
68
+ dir = "#{ENV['HOME']}/.cloudboot"
69
+
70
+ FileUtils.mkdir(dir) unless File.directory?(dir)
71
+
72
+ raise "config #{config_file} already exist" if File.exist?(config_file)
73
+
74
+ File.open(config_file, "w") do |f|
75
+
76
+ cfg = {}
77
+ cfg["database"] = {
78
+ "adapter" => "mysql",
79
+ "host" => "localhost",
80
+ "port" => 3306,
81
+ "username" => "root",
82
+ "password" => "root",
83
+ "database" => "cloudboot",
84
+ "encoding" => "utf8"
85
+ }
86
+ cfg["server"] = {
87
+ "host" => `hostname`.strip,
88
+ "port" => 4567,
89
+ "domain" => "mydomain"
90
+ }
91
+
92
+ f.puts(cfg.to_yaml)
93
+ end
94
+
95
+ when :list_instances
96
+ require 'activerecord'
97
+ ActiveRecord::Base.establish_connection(config["database"])
98
+
99
+ Domain.find_by_name(config["server"]["domain"]).instances.each do |instance|
100
+ puts "#{instance.name},#{instance.fqdn},#{instance.public_ip},#{instance.private_ip},#{instance.refresh.strftime('%Y-%m-%d %H:%m:%S')}"
101
+ end
102
+
103
+ when :userdata
104
+ puts config["server"].to_yaml
105
+
106
+ when :server
107
+ File.open("cloudboot-server.rb", "w") do |f|
108
+ f.puts Cloudboot::Generator.server_script(config)
109
+ end
110
+
111
+ when :client
112
+ File.open("cloudboot-client.rb", "w") do |f|
113
+ f.puts Cloudboot::Generator.bootstrap_script
114
+ end
115
+
116
+ when :cronjob
117
+ ruby = `which ruby`.strip
118
+ ruby = "/path/to/ruby" if ruby =~ /^which: no ruby/
119
+
120
+ puts "3 * * * * #{ruby} #{ENV['HOME']}/cloudboot-client.rb"
121
+
122
+ else
123
+ puts opts.banner
124
+ end
125
+ end
126
+
127
+ def config_file
128
+ "#{ENV['HOME']}/.cloudboot/#{@options[:config]}"
129
+ end
130
+
131
+ def config_exist?
132
+ File.exist?(config_file)
133
+ end
134
+
135
+ end
136
+ end
137
+
138
+ Cloudboot::Bin.new(options).run
data/lib/cloudboot.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'cloudboot/bootstrap'
2
+ require 'cloudboot/generator'
3
+ require 'cloudboot/domain'
@@ -0,0 +1,127 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Cloudboot
7
+
8
+ class Callback
9
+ def self.success(options)
10
+ end
11
+ def self.failure(options)
12
+ end
13
+ end
14
+
15
+ class Bootstrap
16
+ def read(url, options={})
17
+
18
+ options = {
19
+ :params => ""
20
+ }.merge(options)
21
+
22
+ url = URI.parse(url)
23
+
24
+ http = Net::HTTP.new(url.host, url.port)
25
+ http.open_timeout = 10
26
+ http.read_timeout = 10
27
+
28
+ params = options[:params]
29
+
30
+ puts "options: #{options.inspect}"
31
+
32
+ res = if params.empty?
33
+ puts "req: GET #{url}"
34
+ http.get(url.path)
35
+ else
36
+ puts "req: POST #{url} - #{params}"
37
+ http.post(url.path, params)
38
+ end
39
+
40
+ s = if res.is_a?(Net::HTTPOK)
41
+ r = res.body.strip
42
+ r.empty? ? nil : r
43
+ else
44
+ nil
45
+ end
46
+
47
+ puts "res: #{s.nil? ? 'nil' : s}"
48
+
49
+ s
50
+ end
51
+
52
+ def hostname
53
+ `hostname`.strip
54
+ end
55
+
56
+ def fqdn
57
+ `hostname -f`.strip
58
+ end
59
+
60
+ def mutex(lock_file, &block)
61
+
62
+ unless File.exist?(lock_file)
63
+ begin
64
+ FileUtils.touch(lock_file)
65
+ block.call
66
+ ensure
67
+ FileUtils.rm(lock_file)
68
+ end
69
+ else
70
+ puts "skipping - lock_file #{lock_file} exist"
71
+ end
72
+
73
+ end
74
+
75
+ def success(options)
76
+ end
77
+
78
+ def failure(options)
79
+ end
80
+
81
+ def bootstrap
82
+
83
+ mutex "/tmp/cloudboot.lock" do
84
+
85
+ domain = nil
86
+ hostname = nil
87
+ error = nil
88
+
89
+ begin
90
+ user_data = read('http://169.254.169.254/1.0/user-data')
91
+
92
+ if user_data
93
+ bootstrap = YAML.load(user_data)
94
+
95
+ domain = bootstrap['domain']
96
+
97
+ public_ipv4 = read("http://169.254.169.254/latest/meta-data/public-ipv4")
98
+ local_ipv4 = read("http://169.254.169.254/latest/meta-data/local-ipv4")
99
+
100
+ puts bootstrap.inspect
101
+
102
+ url = "http://#{bootstrap['host']}:#{bootstrap['port']}/register"
103
+
104
+ params = "public_ip=#{public_ipv4}&private_ip=#{local_ipv4}&domain=#{domain}&host="
105
+ params << hostname()
106
+
107
+ hostname = read(url, {:params => params})
108
+ end
109
+
110
+ rescue Exception => e
111
+ error = e
112
+ ensure
113
+ options = {:hostname => hostname, :domain => domain, :error => error}
114
+
115
+ if hostname && domain
116
+ Callback.success(options)
117
+ else
118
+ Callback.failure(options)
119
+ end
120
+
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+
127
+ end
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require 'cloudboot/instance'
4
+
5
+ module Cloudboot
6
+ class Domain < ActiveRecord::Base
7
+ has_many :instances, :class_name => "Cloudboot::Instance"
8
+
9
+ def self.ensure(name, instance_prefix, num)
10
+ ActiveRecord::Base.transaction do
11
+
12
+ domain = find_or_create_by_name(name) do |d|
13
+ d.instance_prefix = instance_prefix
14
+ end
15
+
16
+ raise "domain '#{name}' has a different instance_prefix '#{domain.instance_prefix}' <=> '#{instance_prefix}'" if domain.instance_prefix != instance_prefix
17
+
18
+ num.times do |i|
19
+ hostname = "#{domain.instance_prefix}#{(i+1).to_s.rjust(3, '0')}"
20
+ now = DateTime.now
21
+
22
+ domain.instances.find_or_create_by_name(hostname) do |instance|
23
+ instance.public_ip = "0.0.0.0"
24
+ instance.private_ip = "0.0.0.0"
25
+ instance.refresh = now
26
+ instance.created_at = now
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+
34
+ def self.register(domainname, hostname, public_ip, private_ip)
35
+
36
+ puts "registering:"
37
+ puts " domain: #{domainname}"
38
+ puts " hostname: #{hostname}"
39
+ puts " public_ip: #{public_ip}"
40
+ puts " private_ip: #{private_ip}"
41
+
42
+ domain = find_by_name(domainname)
43
+ raise "unknown domain: #{domainname}" unless domain
44
+
45
+ instance = domain.instances.find_by_name_and_public_ip_and_private_ip(hostname, public_ip, private_ip)
46
+
47
+ now = DateTime.now
48
+
49
+ unless instance
50
+ instance = domain.instances.find(:first, :conditions => ["refresh < ? or refresh = created_at", now - 60.seconds], :order => "name asc")
51
+ end
52
+
53
+ raise "no available instance in domain: #{domainname}" unless instance
54
+
55
+ ActiveRecord::Base.transaction do
56
+ instance.public_ip = public_ip
57
+ instance.private_ip = private_ip
58
+ instance.refresh = now
59
+ instance.save
60
+ end
61
+
62
+ instance
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,127 @@
1
+ require 'yaml'
2
+
3
+ module Cloudboot
4
+ class Generator
5
+
6
+ def self.bootstrap_script
7
+ <<-EOF
8
+ #!/usr/bin/ruby
9
+
10
+ require 'rubygems'
11
+ require 'cloudboot'
12
+
13
+ module Cloudboot
14
+ class Callback
15
+
16
+ def self.failure(options)
17
+ puts "cloudboot failure"
18
+ puts options[:error].inspect if options[:error]
19
+ end
20
+
21
+ def self.success(options)
22
+
23
+ hostname = options[:hostname]
24
+ domain = options[:domain]
25
+
26
+ puts "setup - hostname: \#{hostname}, domain: \#{domain}"
27
+
28
+ `echo "127.0.0.1 \#{hostname}.\#{domain} localhost \#{hostname}" > /etc/hosts`
29
+ `sed -i 's/HOSTNAME=.*/HOSTNAME=\"\#{hostname}\"/g' /etc/rc.conf`
30
+
31
+ `hostname \#{hostname}`
32
+ end
33
+ end
34
+ end
35
+
36
+ Cloudboot::Bootstrap.new.bootstrap
37
+ EOF
38
+ end
39
+
40
+ def self.server_script(options)
41
+
42
+ database = options["database"]
43
+ server = options["server"]
44
+
45
+ <<-EOF
46
+ #!/usr/bin/ruby
47
+
48
+ require 'rubygems'
49
+ require 'activerecord'
50
+ require 'sinatra'
51
+ require 'cloudboot'
52
+
53
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
54
+
55
+ ActiveRecord::Base.establish_connection({
56
+ :adapter => '#{database["adapter"]}',
57
+ :encoding => '#{database["encoding"]}',
58
+ :database => '#{database["database"]}',
59
+ :host => '#{database["host"]}',
60
+ :port => #{database["port"]},
61
+ :username => '#{database["username"]}',
62
+ :password => '#{database["password"]}'
63
+ })
64
+
65
+ Cloudboot::Generator.setup_tables
66
+
67
+ Cloudboot::Domain.ensure("#{server["domain"]}", "srv", 3)
68
+
69
+ set :server, ["webrick"]
70
+ set :environment, :production
71
+ set :lock, true
72
+
73
+ post "/register" do
74
+
75
+ public_ip = request.params['public_ip']
76
+ private_ip = request.params['private_ip']
77
+ domain = request.params['domain']
78
+ hostname = request.params['host']
79
+
80
+ instance = Cloudboot::Domain.register(domain, hostname, public_ip, private_ip)
81
+ instance.name
82
+ end
83
+ EOF
84
+ end
85
+
86
+ def self.table_exist?(table_name)
87
+ ActiveRecord::Base.connection.select_value("show tables like '#{table_name}'") != nil
88
+ end
89
+
90
+ def self.setup_tables
91
+
92
+ unless table_exist?("domains")
93
+ ActiveRecord::Base.connection.execute <<-SQL
94
+ create
95
+ table domains
96
+ (
97
+ id bigint not null AUTO_INCREMENT,
98
+ name varchar(255) not null,
99
+ instance_prefix varchar(255) not null,
100
+ primary key (id)
101
+ )
102
+ ENGINE=InnoDB default CHARSET=utf8
103
+ SQL
104
+ end
105
+
106
+ unless table_exist?("instances")
107
+ ActiveRecord::Base.connection.execute <<-SQL
108
+ create
109
+ table instances
110
+ (
111
+ id bigint not null AUTO_INCREMENT,
112
+ name varchar(255) not null,
113
+ domain_id bigint not null,
114
+ public_ip varchar(15) not null,
115
+ private_ip varchar(15) not null,
116
+ refresh datetime not null,
117
+ created_at datetime not null,
118
+ primary key (id)
119
+ )
120
+ ENGINE=InnoDB default CHARSET=utf8
121
+ SQL
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,9 @@
1
+ module Cloudboot
2
+ class Instance < ActiveRecord::Base
3
+ belongs_to :domain, :class_name => "Cloudboot::Domain"
4
+
5
+ def fqdn
6
+ "#{name}.#{domain.name}"
7
+ end
8
+ end
9
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'cloudboot'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,6 @@
1
+ require 'helper'
2
+
3
+ class TestCloudboot < Test::Unit::TestCase
4
+ should "success" do
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudboot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Zimmek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-06 00:00:00 +01:00
13
+ default_executable: cloudboot
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: cloudboot is the missing glue between your bare bone ec2 instance and a configuration management tool like cfengine, puppet or chef.
36
+ email: jan.zimmek@web.de
37
+ executables:
38
+ - cloudboot
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - bin/cloudboot
46
+ - lib/cloudboot.rb
47
+ - lib/cloudboot/bootstrap.rb
48
+ - lib/cloudboot/domain.rb
49
+ - lib/cloudboot/generator.rb
50
+ - lib/cloudboot/instance.rb
51
+ - LICENSE
52
+ - README.rdoc
53
+ has_rdoc: true
54
+ homepage: http://github.com/jzimmek/cloudboot
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.5
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: simplify the bootstrapping of your amazone ec2 instances
81
+ test_files:
82
+ - test/helper.rb
83
+ - test/test_cloudboot.rb