annex 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ config/settings.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in annex.gemspec
4
+ gemspec
@@ -0,0 +1,40 @@
1
+ == Annex
2
+
3
+ Annex leverages chef-solo to allow you to provision and update multiple
4
+ servers by looking up network topology on the fly utilizing a distributed
5
+ repository to manage recipes.
6
+
7
+ == Getting started
8
+
9
+ The `annex` command allows you to provision a server (bootstrapping or
10
+ updating as needed) or list the servers that you have already provisioned.
11
+
12
+ Usage: annex [-v] [-h] command [<args>]
13
+
14
+ Available commands:
15
+
16
+ provision
17
+ list
18
+
19
+ Global options:
20
+
21
+ -h, --help Show this message
22
+ -v, --version Show version
23
+
24
+ In order for this to work, your recipes have to be setup and your
25
+ config/settings.yml needs to be setup.
26
+
27
+ == Testing
28
+
29
+ Nope.
30
+
31
+ == Contributors
32
+
33
+ * Jeff Rafter
34
+ * Your name here
35
+
36
+ == Acknowledgements
37
+
38
+ Thanks to Mitchell Hashimoto ({@mitchellh}[link:https://twitter.com/#!/mitchellh]) and
39
+ Nick Plante ({@zapnap}[link:https://twitter.com/#!/zapnap]).
40
+
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "annex/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "annex"
7
+ s.version = Annex::VERSION
8
+ s.authors = ["Jeff Rafter"]
9
+ s.email = ["jeffrafter@gmail.com"]
10
+ s.homepage = "http://github.com/jeffrafter/annex"
11
+ s.summary = %q{Quickly provision servers using chef-solo and no central server.}
12
+ s.description = %q{Annex leverages chef-solo to allow you to provision and update mutliple servers by looking up network topology on the fly utilizing a distributed repository to manage recipes"}
13
+
14
+ s.rubyforge_project = "annex"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency("fog", [">= 0"])
22
+ s.add_runtime_dependency("json", [">= 0"])
23
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap interrupts to quit cleanly.
4
+ Signal.trap("INT") { exit 1 }
5
+
6
+ require 'annex'
7
+ require 'annex/cli'
8
+
9
+ # Stdout/stderr should not buffer output
10
+ $stdout.sync = true
11
+ $stderr.sync = true
12
+
13
+ # Initialization options
14
+ opts = {}
15
+
16
+ # Are we running on Windows?
17
+ opts[:windows] = RbConfig::CONFIG["host_os"].downcase =~ /(mingw|mswin)/
18
+
19
+ # Disable color if the proper argument was passed or if we're
20
+ # on Windows since the default Windows terminal doesn't support
21
+ # colors.
22
+ opts[:support_colors] = opts[:windows] && ENV.has_key?("ANSICON")
23
+ opts[:no_colors] = ARGV.include?("--no-color") || !$stdout.tty? || !opts[:supports_colors]
24
+ ARGV.delete("--no-color")
25
+
26
+ env = nil
27
+ begin
28
+ # Create the environment, which is the cwd of wherever the
29
+ # `annex` command was invoked from
30
+ env = Annex::Environment.new(opts)
31
+
32
+ # Execute the CLI interface, and exit with the proper error code
33
+ exit(env.cli(ARGV))
34
+
35
+ rescue Annex::Errors::AnnexError => e
36
+ if env
37
+ env.ui.error e.message, {:prefix => false} if e.message
38
+ else
39
+ $stderr.puts "Annex failed to initialize at a very early stage:\n\n"
40
+ $stderr.puts e.message
41
+ end
42
+
43
+ exit e.status_code if e.respond_to?(:status_code)
44
+
45
+ # An error occurred with no status code defined
46
+ exit 999
47
+ end
@@ -0,0 +1,61 @@
1
+ repository: YOUR_PRIVATE_REPO
2
+ users:
3
+ bootstrap: ubuntu
4
+ update: youruser
5
+ amazon:
6
+ access_key_id: AMAZON_KEY_HERE
7
+ secret_access_key: AMAZON_SECRET_HERE
8
+ images:
9
+ ebs:
10
+ image_id: ami-63be790a
11
+ flavor_id: m2.2xlarge
12
+ ec2:
13
+ image_id: ami-35de095c
14
+ flavor_id: m1.large
15
+ ec2_small_32:
16
+ image_id: ami-81b275e8
17
+ flavor_id: m1.small
18
+ ebs_micro:
19
+ image_id: ami-71dc0b18
20
+ flavor_id: t1.micro
21
+ windows:
22
+ image_id: ami-92ba43fb
23
+ flavor_id: m1.small
24
+ roles:
25
+ web:
26
+ image: ec2
27
+ count: 2
28
+ app:
29
+ image: ec2
30
+ count: 1
31
+ utility:
32
+ image: ec2
33
+ count: 1
34
+ redis:
35
+ image: ec2
36
+ count: 1
37
+ "redis-slave":
38
+ image: ec2
39
+ count: 1
40
+ ci:
41
+ ruby: package
42
+ image: ec2_small_32
43
+ count: 1
44
+ qa:
45
+ image: ec2
46
+ count: 1
47
+ dns:
48
+ ruby: package
49
+ image: ebs_micro
50
+ count: 1
51
+ windows:
52
+ image: windows
53
+ count: 1
54
+ staging:
55
+ image: ec2
56
+ count: 1
57
+ monitoring:
58
+ ruby: package
59
+ image: ec2_small_32
60
+ count: 1
61
+
@@ -0,0 +1,9 @@
1
+ require "annex/version"
2
+ require "annex/errors"
3
+ require "annex/environment"
4
+ require "annex/command"
5
+ require "annex/list"
6
+ require "annex/provision"
7
+
8
+ module Annex
9
+ end
@@ -0,0 +1,119 @@
1
+ require 'optparse'
2
+
3
+ module Annex
4
+ # Manages the command line interface to Annex
5
+ class CLI
6
+ COMMANDS = %w(provision list)
7
+
8
+ def initialize(argv, env)
9
+ @argv = argv
10
+ @env = env
11
+ end
12
+
13
+ def execute
14
+ exit_code = 0
15
+ return exit_code unless options = parse(@argv.dup)
16
+ command = case options[:command]
17
+ when "provision"
18
+ Annex::Provision.new(@env, options)
19
+ when "list"
20
+ Annex::List.new(@env, options)
21
+ end
22
+ env.info("Executing #{options[:command]}", :command)
23
+ command.execute
24
+ exit_code
25
+ end
26
+
27
+ private
28
+
29
+ def env
30
+ @env
31
+ end
32
+
33
+ def parse(argv)
34
+ # Global option parser
35
+ parser = OptionParser.new do |opts|
36
+ opts.banner = "Usage: annex [-v] [-h] command [<args>]"
37
+ opts.separator ""
38
+ opts.separator "Available commands: "
39
+ opts.separator ""
40
+
41
+ COMMANDS.each do |c| opts.separator " #{c}" end
42
+
43
+ opts.separator ""
44
+ opts.separator "Global options:"
45
+ opts.separator ""
46
+
47
+ opts.on_tail("-h", "--help", "Show this message") do
48
+ env.info opts
49
+ return
50
+ end
51
+
52
+ opts.on_tail("-v", "--version", "Show version") do
53
+ env.info "annex version #{Annex::VERSION}"
54
+ return
55
+ end
56
+ end
57
+
58
+ # If there were no options then we show the usage and exit
59
+ if argv.nil? || argv.length == 0
60
+ env.info parser
61
+ return
62
+ end
63
+
64
+ # Grab the first arg, and setup the command options hash
65
+ options = {:command => argv.shift}
66
+
67
+ # Verify the commands
68
+ parser.order!([options[:command]]) do |unknown|
69
+ next if COMMANDS.include?(unknown)
70
+ env.error "Unknown command #{unknown.inspect}"
71
+ env.info opts
72
+ return
73
+ end
74
+
75
+ # Create a command specific parser
76
+ parser = case options[:command]
77
+ when "provision"
78
+ OptionParser.new do |opts|
79
+ opts.banner = "Usage: annex provision <args>"
80
+ opts.on("-r ROLE", "--role ROLE", "Specify the role for the server you are provisioning") do |role|
81
+ options[:role] = role
82
+ end
83
+ opts.on("-e ENVIRONMENT", "--environment ENVIRONMENT", "Specify the environment for the server you are provisioning") do |environment|
84
+ options[:environment] = environment
85
+ end
86
+ end
87
+ when "list"
88
+ OptionParser.new do |opts|
89
+ opts.banner = "Usage: annex list <args>"
90
+ opts.on("-r ROLE", "--role ROLE", "List all servers matching the specified role") do |role|
91
+ options[:role] = role
92
+ end
93
+ opts.on("-e ENVIRONMENT", "--environment ENVIRONMENT", "List all servers in the specified environment") do |environment|
94
+ options[:environment] = environment
95
+ end
96
+ opts.on("-a", "--all", "List all servers in all environments") do
97
+ options[:all] = true
98
+ end
99
+ end
100
+ end
101
+
102
+ # If there were no command options then we show the usage and exit
103
+ if argv.length == 0
104
+ env.info parser
105
+ return
106
+ end
107
+
108
+ # Verify the commands
109
+ parser.parse!(argv) do |unknown|
110
+ env.error "Unknown option for #{options[:command]} command: #{unknown.inspect}"
111
+ env.info opts
112
+ return
113
+ end
114
+
115
+ # Send back the parsed options
116
+ options
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,46 @@
1
+ require 'fog'
2
+
3
+ # Need some better logging
4
+ require 'mixins/fog'
5
+
6
+ module Annex
7
+ class Command
8
+ def initialize(env, options)
9
+ @env = env
10
+ @options = options
11
+ end
12
+
13
+ def execute
14
+ raise NotImplementedError
15
+ end
16
+
17
+ protected
18
+
19
+ def template(name, template_binding=nil)
20
+ content = File.read(File.join(File.expand_path(File.dirname(__FILE__)),"..","..","templates","#{name}.erb")) rescue nil
21
+ erb = ERB.new(content)
22
+ erb.result(template_binding || binding)
23
+ end
24
+
25
+ def connection
26
+ @connection = Fog::Compute.new({
27
+ :provider => 'AWS',
28
+ :region => @env.config['amazon']['region'] || 'us-east-1',
29
+ :aws_access_key_id => @env.config['amazon']['access_key_id'],
30
+ :aws_secret_access_key => @env.config['amazon']['secret_access_key']
31
+ })
32
+ end
33
+
34
+ def servers
35
+ @servers ||= connection.servers.all
36
+ end
37
+
38
+ def role
39
+ @role ||= @options[:role]
40
+ end
41
+
42
+ def environment
43
+ @environment ||= @options[:environment]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,83 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ module Annex
5
+ # Represents an Annex environment. The basic annex environment contains
6
+ # a config/settings.yml file defining the server cluster.
7
+ class Environment
8
+
9
+ # The `cwd` that this environment represents
10
+ attr_reader :cwd
11
+
12
+ # Initializes a new environment with the given options. The options
13
+ # is a hash where the main available key is `cwd`, which defines the
14
+ # location of the environment. If `cwd` is nil, then it defaults
15
+ # to the `Dir.pwd` (which is the cwd of the executing process).
16
+ def initialize(opts=nil)
17
+ opts = {
18
+ :cwd => nil,
19
+ :windows => false,
20
+ :supports_colors => true,
21
+ :no_colors => false
22
+ }.merge(opts || {})
23
+
24
+ # Set the default working directory
25
+ opts[:cwd] ||= ENV["ANNEX_CWD"] if ENV.has_key?("ANNEX_CWD")
26
+ opts[:cwd] ||= Dir.pwd
27
+ opts[:cwd] = Pathname.new(opts[:cwd])
28
+ raise Errors::AnnexError.new("Unknown current working directory") if !opts[:cwd].directory?
29
+
30
+ # Set instance variables for all the configuration parameters.
31
+ @cwd = opts[:cwd]
32
+ @colorize = opts[:supports_colors] || !opts[:no_colors]
33
+ end
34
+
35
+ # Return a human-friendly string for pretty printed or inspected
36
+ # instances.
37
+ #
38
+ # @return [String]
39
+ def inspect
40
+ "#<#{self.class}: #{@cwd}>"
41
+ end
42
+
43
+ # Makes a call to the CLI with the given arguments as if they
44
+ # came from the real command line (sometimes they do!). An example:
45
+ #
46
+ # env.cli("provision", "--role", "app", "--environment", "staging")
47
+ #
48
+ def cli(*args)
49
+ CLI.new(args.flatten, self).execute
50
+ end
51
+
52
+ # The configuration object represented by this environment. This
53
+ # will trigger the environment to load if it hasn't loaded yet.
54
+ #
55
+ # @return [hash]
56
+ def config
57
+ @config ||= YAML::load_file(File.join(@cwd, "config", "settings.yml"))
58
+ end
59
+
60
+ # Output a message, formatted if we support colors
61
+ def info(message, level=:default)
62
+ $stdout.puts colorize(message, level)
63
+ end
64
+
65
+ # Output a message, formatted if we support colors
66
+ def error(message)
67
+ $stderr.puts colorize(message, :error)
68
+ end
69
+
70
+ private
71
+
72
+ def colorize(message, level=:info)
73
+ # Terminal colors
74
+ colors = {
75
+ :error => "\e[31m", # red
76
+ :info => "\e[32m", # green
77
+ :command => "\e[33m", # yellow
78
+ :notice => "\e[34m" # blue
79
+ }
80
+ @colorize ? "#{colors[level]}#{message}\e[0m" : message
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,6 @@
1
+ module Annex
2
+ module Errors
3
+ class AnnexError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Annex
2
+ class List < Command
3
+ def execute
4
+ which = servers.select do |server|
5
+ name_tag = server.tags["Name"]
6
+ name_tag = name_tag.gsub(/-i-[0-9a-f]+$/, '') rescue ''
7
+
8
+ choose = server.state == "running"
9
+ choose = choose && name_tag =~ /^#{role}/ if role
10
+ choose = choose && name_tag =~ /#{environment}$/ if environment
11
+ choose
12
+ end
13
+
14
+ which.each do |server|
15
+ @env.info(server.tags["Name"], :info)
16
+ @env.info(" #{server.dns_name}\n Public: #{server.public_ip_address}\n Private: #{server.private_ip_address}\n", :notice)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,237 @@
1
+ require 'erb'
2
+ require 'json'
3
+ require 'tempfile'
4
+
5
+ module Annex
6
+ class Provision < Command
7
+ def execute
8
+ if !role && !environment
9
+ @env.info("Sorry, you must include the role and the environment when provisioning", :error)
10
+ return
11
+ end
12
+
13
+ begin
14
+ # Try to determine the environment
15
+ write_environment
16
+
17
+ which = servers.select do |server|
18
+ name_tag = server.tags["Name"]
19
+ name_tag = name_tag.gsub(/-i-[0-9a-f]+$/, '') rescue ''
20
+
21
+ choose = server.state == "running"
22
+ choose = choose && name_tag =~ /^#{role}/ if role
23
+ choose = choose && name_tag =~ /#{environment}$/ if environment
24
+ choose
25
+ end
26
+
27
+ msg = "We have #{which.length} servers"
28
+ msg << " for the #{role} role"
29
+ msg << " in the environment #{environment}"
30
+ @env.info(msg, :info)
31
+
32
+ count = @env.config['roles'][role]['count']
33
+ @env.info("We should have #{count}", :info)
34
+
35
+ # Try to be graceful
36
+ Thread.abort_on_exception = false
37
+ threads = []
38
+
39
+ # For each server we do have, update
40
+ which.each do |server|
41
+ threads << update(server, @env.config['users']['update']) unless ENV['SKIP_UPDATE']
42
+ end
43
+
44
+ # For each server we need, bootstrap
45
+ (count - which.length).times do
46
+ image = @env.config['roles'][role]['image']
47
+ kind = @env.config['amazon']['images'][image]
48
+ threads << bootstrap(connection, kind['image_id'], kind['flavor_id'], kind['az_id'], @env.config['users']['bootstrap'])
49
+ end
50
+
51
+ threads.each do |thr|
52
+ thr.join unless ENV['SYNC']
53
+ end
54
+
55
+ ensure
56
+ cleanup_environment
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def ruby_script
63
+ if @env.config['roles'][role]['ruby'] == "package"
64
+ template("ruby-apt.sh")
65
+ elsif @env.config['roles'][role]['ruby'] == "1.9.3"
66
+ template("ruby-1.9.3.sh")
67
+ else
68
+ template("ruby-ree.sh")
69
+ end
70
+ end
71
+
72
+ # Bootstrap the environment with chef to handle chef-roles
73
+ def bootstrap_script(options={})
74
+ template_binding = OpenStruct.new(options)
75
+ template("bootstrap.sh", template_binding.instance_eval { binding })
76
+ end
77
+
78
+ # Bootstrap the environment with chef to handle chef-roles
79
+ def update_script(options={})
80
+ template_binding = OpenStruct.new(options)
81
+ template("update.sh", template_binding.instance_eval { binding })
82
+ end
83
+
84
+ def bootstrap(connection, image_id, flavor_id, az_id, user)
85
+ thr = Thread.new(connection, image_id, flavor_id, az_id, user) do |_connection, _image_id, _flavor_id, _az_id, _user|
86
+ sleep 1
87
+
88
+ @env.info("Bootstrapping #{role} server...", :info)
89
+
90
+ begin
91
+ # Build the server from the base AMI
92
+ server = _connection.servers.bootstrap({
93
+ :private_key_path => '~/.ssh/id_rsa',
94
+ :public_key_path => '~/.ssh/id_rsa.pub',
95
+ :availability_zone => _az_id,
96
+ :username => _user,
97
+ :image_id => _image_id,
98
+ :flavor_id => _flavor_id
99
+ })
100
+
101
+ node_name = "#{role}-#{environment}-#{server.identity}"
102
+
103
+ # Add the tags
104
+ _connection.tags.create(
105
+ :resource_id => server.identity,
106
+ :key => 'Name',
107
+ :value => node_name)
108
+ rescue Exception => e
109
+ @env.error("Error for #{server.id} (#{e.message})\n\n#{e.backtrace}", :error)
110
+ return
111
+ end
112
+
113
+ scp_options = { :forward_agent => true }
114
+ scp = Fog::SCP.new(server.public_ip_address, server.username, scp_options)
115
+ scp.upload(@envfile.path.to_s, "#{environment}.json")
116
+
117
+ ssh_options = { :forward_agent => true }
118
+ ssh = Fog::SSH.new(server.public_ip_address, server.username, ssh_options)
119
+
120
+ begin
121
+ return if _image_id == "windows"
122
+ script = bootstrap_script({
123
+ :environment => environment,
124
+ :node_name => node_name,
125
+ :role => role,
126
+ :user => @env.config['users']['bootstrap'],
127
+ :ruby_script => ruby_script,
128
+ :repository => @env.config['repository']
129
+ })
130
+ script.split(/\n/).each do |cmd|
131
+ next if cmd == ''
132
+ @env.info("")
133
+ @env.info("Running command:", :info)
134
+ @env.info("")
135
+ @env.info(" #{cmd}", :command)
136
+ ssh.run(cmd)
137
+ end
138
+ rescue Exception => e
139
+ @env.error("Error for #{server.id} (#{e.message})\n\n#{e.backtrace}", :error)
140
+ end
141
+
142
+ begin
143
+ @env.info("")
144
+ @env.info("Done", :info)
145
+ @env.info(" #{server.dns_name}", :notice)
146
+ @env.info(" Public: #{server.public_ip_address}", :notice)
147
+ @env.info(" Private: #{server.private_ip_address}", :notice)
148
+ rescue
149
+ end
150
+ end
151
+ thr.join if ENV['SYNC']
152
+ thr
153
+ end
154
+
155
+ def update(server, user)
156
+ thr = Thread.new(server, user) do |_server, _user|
157
+ sleep 1
158
+
159
+ @env.info("Updating #{_server.public_ip_address} (#{_server.id})\n", :info)
160
+
161
+ scp_options = { :forward_agent => true }
162
+ scp = Fog::SCP.new(_server.public_ip_address, _user, scp_options)
163
+ scp.upload(@envfile.path.to_s, "#{environment}.json")
164
+
165
+ ssh_options = { :forward_agent => true }
166
+ ssh = Fog::SSH.new(_server.public_ip_address, _user, ssh_options)
167
+
168
+ node_name = "#{role}-#{environment}-#{_server.identity}"
169
+
170
+ begin
171
+ script = update_script({
172
+ :environment => environment,
173
+ :node_name => node_name,
174
+ :role => role,
175
+ :user => @env.config['users']['update']
176
+ })
177
+ script.split(/\n/).each do |cmd|
178
+ next if cmd == ''
179
+ @env.info("")
180
+ @env.info("Running command:", :info)
181
+ @env.info("")
182
+ @env.info(" #{cmd}", :command)
183
+ ssh.run(cmd)
184
+ end
185
+ rescue Exception => e
186
+ @env.error("Error for #{_server.id} (#{e.message})\n\n#{e.backtrace}", :error)
187
+ end
188
+
189
+ begin
190
+ @env.info("")
191
+ @env.info("Done", :info)
192
+ @env.info(" #{_server.dns_name}", :notice)
193
+ @env.info(" Public: #{_server.public_ip_address}", :notice)
194
+ @env.info(" Private: #{_server.private_ip_address}", :notice)
195
+ rescue
196
+ end
197
+ end
198
+ thr.join if ENV['SYNC']
199
+ thr
200
+ end
201
+
202
+ def write_environment
203
+ @nodes = []
204
+ servers.each do |server|
205
+ next unless server.state == "running"
206
+ next unless server.tags["Name"] && server.tags["Name"] != ''
207
+
208
+ role = server.tags["Name"].gsub(/-.*/, '')
209
+ env = server.tags["Name"].gsub(/^[^-]+-/, '').gsub(/-.*/, '')
210
+ next unless env == environment
211
+
212
+ @nodes << {
213
+ :name => server.tags["Name"],
214
+ :role => role,
215
+ :environment => env,
216
+ :public_fqdn => server.dns_name,
217
+ :public_ip => server.public_ip_address,
218
+ :private_ip => server.private_ip_address
219
+ }
220
+ end
221
+ @nodes
222
+
223
+ @envfile = Tempfile.new("annex-#{environment}.json")
224
+ @envfile.puts({:id => environment, :nodes => @nodes}.to_json)
225
+ @envfile.flush
226
+ @envfile
227
+ end
228
+
229
+ def cleanup_environment
230
+ return unless @envfile
231
+ @envfile.close
232
+ @envfile.unlink
233
+ @envfile = nil
234
+ end
235
+
236
+ end
237
+ end
@@ -0,0 +1,3 @@
1
+ module Annex
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,69 @@
1
+ module Fog
2
+ module SSH
3
+ class Real
4
+ def run(commands)
5
+ commands = [*commands]
6
+ results = []
7
+ begin
8
+ Net::SSH.start(@address, @username, @options) do |ssh|
9
+ commands.each do |command|
10
+ result = Result.new(command)
11
+ ssh.open_channel do |ssh_channel|
12
+ ssh_channel.request_pty
13
+ ssh_channel.exec(command) do |channel, success|
14
+ unless success
15
+ raise "Could not execute command: #{command.inspect}"
16
+ end
17
+
18
+ channel.on_data do |ch, data|
19
+ result.stdout << handle_data(channel, data)
20
+ end
21
+
22
+ channel.on_extended_data do |ch, type, data|
23
+ next unless type == 1
24
+ result.stderr << handle_error(channel, data)
25
+ end
26
+
27
+ channel.on_request('exit-status') do |ch, data|
28
+ result.status = data.read_long
29
+ end
30
+
31
+ channel.on_request('exit-signal') do |ch, data|
32
+ result.status = 255
33
+ end
34
+ end
35
+ end
36
+ ssh.loop
37
+ results << result
38
+ end
39
+ end
40
+ rescue Net::SSH::HostKeyMismatch => exception
41
+ exception.remember_host!
42
+ sleep 0.2
43
+ retry
44
+ end
45
+ results
46
+ end
47
+
48
+ def handle_data(channel, data)
49
+ data
50
+ end
51
+
52
+ def handle_error(channel, error)
53
+ error
54
+ end
55
+ end
56
+
57
+ class Real
58
+ def handle_data(channel, data)
59
+ puts data
60
+ data
61
+ end
62
+
63
+ def handle_error(channel, error)
64
+ puts error.red
65
+ error
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ sudo apt-get update
2
+ sudo apt-get -y install ssh git-core wget curl build-essential clang bison openssl zlib1g libxslt1.1 libssl-dev libxslt1-dev libxml2 libffi-dev libyaml-dev libxslt-dev autoconf libc6-dev libreadline6-dev zlib1g-dev libcurl4-openssl-dev
3
+
4
+ <%= ruby_script %>
5
+
6
+ sudo gem install rake --no-ri --no-rdoc
7
+ sudo gem install slimgems --no-ri --no-rdoc
8
+ sudo gem install chef -v 10.20.0 --no-ri --no-rdoc
9
+
10
+ sudo mkdir -p /var/chef-solo
11
+ sudo chown <%= user %> /var/chef-solo
12
+
13
+ echo "github.com,207.97.227.239 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts
14
+
15
+ git clone <%= repository %> /var/chef-solo/cookbooks
16
+ cp <%= environment %>.json /var/chef-solo/cookbooks/data_bags/environments/
17
+
18
+ sudo mkdir -p /etc/chef
19
+ sudo chown <%= user %> /etc/chef
20
+ cp /var/chef-solo/cookbooks/solo.rb /etc/chef
21
+ echo 'node_name "<%= node_name %>"' >> /etc/chef/solo.rb
22
+ echo '{ "run_list": ["role[<%= role %>]"]}' > /etc/chef/dna.json
23
+
24
+ sudo chef-solo -c /etc/chef/solo.rb -j /etc/chef/dna.json
@@ -0,0 +1,5 @@
1
+ wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p0.tar.gz
2
+ tar -xzf ruby-1.9.3-p0.tar.gz 2>&1
3
+ cd ruby-1.9.3-p0 && ./configure --prefix=/usr/local 2>&1 \
4
+ && make 2>&1 \
5
+ && sudo make install 2>&1
@@ -0,0 +1,5 @@
1
+ sudo apt-get -y install ruby ruby-dev libopenssl-ruby1.8 irb ri rdoc
2
+ wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz
3
+ tar xvzf rubygems-1.3.7.tgz
4
+ cd rubygems-1.3.7 && sudo ruby setup.rb
5
+ sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
@@ -0,0 +1,2 @@
1
+ wget http://rubyenterpriseedition.googlecode.com/files/ruby-enterprise_1.8.7-2011.03_amd64_ubuntu10.04.deb
2
+ sudo dpkg -i ruby-enterprise_1.8.7-2011.03_amd64_ubuntu10.04.deb
@@ -0,0 +1,10 @@
1
+ sudo chown -R <%= user %>:<%= user %> /var/chef-solo
2
+ sudo chown -R <%= user %>:<%= user %> /etc/chef
3
+ cd /var/chef-solo/cookbooks && git pull origin master
4
+ cp <%= environment %>.json /var/chef-solo/cookbooks/data_bags/environments
5
+
6
+ cp /var/chef-solo/cookbooks/solo.rb /etc/chef
7
+ echo 'node_name "<%= node_name %>"' >> /etc/chef/solo.rb
8
+ echo '{ "run_list": ["role[<%= role %>]"]}' > /etc/chef/dna.json
9
+
10
+ sudo chef-solo -c /etc/chef/solo.rb -j /etc/chef/dna.json
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: annex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Jeff Rafter
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-06-28 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: fog
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: Annex leverages chef-solo to allow you to provision and update mutliple servers by looking up network topology on the fly utilizing a distributed repository to manage recipes"
50
+ email:
51
+ - jeffrafter@gmail.com
52
+ executables:
53
+ - annex
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - README.rdoc
62
+ - Rakefile
63
+ - annex.gemspec
64
+ - bin/annex
65
+ - config/settings.yml.example
66
+ - lib/annex.rb
67
+ - lib/annex/cli.rb
68
+ - lib/annex/command.rb
69
+ - lib/annex/environment.rb
70
+ - lib/annex/errors.rb
71
+ - lib/annex/list.rb
72
+ - lib/annex/provision.rb
73
+ - lib/annex/version.rb
74
+ - lib/mixins/fog.rb
75
+ - templates/bootstrap.sh.erb
76
+ - templates/ruby-1.9.3.sh.erb
77
+ - templates/ruby-apt.sh.erb
78
+ - templates/ruby-ree.sh.erb
79
+ - templates/update.sh.erb
80
+ has_rdoc: true
81
+ homepage: http://github.com/jeffrafter/annex
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project: annex
110
+ rubygems_version: 1.6.2
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Quickly provision servers using chef-solo and no central server.
114
+ test_files: []
115
+