annex 0.0.2

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