hetzner-bootstrap 0.0.1 → 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,32 @@
1
+ = hetzner-bootstrap
2
+
3
+ hetzner-bootstrap allows you to bootstrap a provisioned EQ Server from hetzner.de
4
+
5
+ Requirements
6
+
7
+ - get a webservice login from your customer panel (https://robot.your-server.de/)
8
+ - the ip address of the shipped system(s)
9
+
10
+ == Installation
11
+
12
+ <tt> gem install hetzner-bootstrap</tt>
13
+
14
+ == Example
15
+
16
+ <b>see example.rb for usage!</b>
17
+
18
+ == WARNING!
19
+
20
+ This is not an official Hetzner AG project.
21
+
22
+ The gem and the author are not related to Hetzner AG!
23
+
24
+ <b>Use at your very own risk! Satisfaction NOT guaranteed!</b>
25
+
26
+ == Copyright
27
+
28
+ Copyright (c) 2011 Moriz GmbH, Roland Moriz. See LICENSE for details.
29
+
30
+ {Ruby on Rails Entwicklung}[http://moriz.de/] -> Moriz GmbH
31
+
32
+ {Ruby on Rails Hosting}[http://rails.io/] -> Rails.io
data/example.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ require "rubygems"
2
3
  require "hetzner-bootstrap"
3
4
 
4
5
  API_USERNAME="xxx"
@@ -1,4 +1,5 @@
1
1
  require 'benchmark'
2
+ require 'logger'
2
3
 
3
4
  require 'hetzner-api'
4
5
  require 'hetzner/bootstrap/version'
@@ -9,21 +10,25 @@ module Hetzner
9
10
  class Bootstrap
10
11
  attr_accessor :targets
11
12
  attr_accessor :api
12
- attr_accessor :use_threads
13
13
  attr_accessor :actions
14
+ attr_accessor :logger
14
15
 
15
16
  def initialize(options = {})
16
17
  @targets = []
17
18
  @actions = %w(enable_rescue_mode
18
19
  reset
19
- wait_for_ssh
20
+ wait_for_ssh_down
21
+ wait_for_ssh_up
20
22
  installimage
21
- wait_for_ssh
23
+ reboot
24
+ wait_for_ssh_down
25
+ wait_for_ssh_up
22
26
  verify_installation
23
27
  copy_ssh_keys
24
28
  post_install)
29
+ #@actions = %w(wait_for_ssh_down)
25
30
  @api = options[:api]
26
- @use_threads = options[:use_threads] || true
31
+ @logger = options[:logger] || Logger.new(STDOUT)
27
32
  end
28
33
 
29
34
  def add_target(param)
@@ -37,50 +42,31 @@ module Hetzner
37
42
  def <<(param)
38
43
  add_target param
39
44
  end
40
-
41
- def bootstrap!(options = {})
42
- threads = []
43
45
 
46
+ def bootstrap!(options = {})
44
47
  @targets.each do |target|
45
- target.use_api @api
46
-
47
- if uses_threads?
48
- threads << Thread.new do
49
- bootstrap_one_target! target
50
- end
51
- else
48
+ #fork do
49
+ target.use_api @api
50
+ target.use_logger @logger
52
51
  bootstrap_one_target! target
53
- end
52
+ #end
54
53
  end
55
-
56
- finalize_threads(threads) if uses_threads?
54
+ #Process.waitall
57
55
  end
58
56
 
59
57
  def bootstrap_one_target!(target)
60
58
  actions = (target.actions || @actions)
61
59
  actions.each_with_index do |action, index|
62
-
63
- log target.ip, action, index, 'START'
60
+
61
+ loghack = "\b" * 24 # remove: "[bootstrap_one_target!] ".length
62
+ target.logger.info "#{loghack}[#{action}] #{sprintf "%-20s", "START"}"
64
63
  d = Benchmark.realtime do
65
64
  target.send action
66
65
  end
67
-
68
- log target.ip, action, index, "FINISHED in #{sprintf "%.5f",d} seconds"
66
+ target.logger.info "#{loghack}[#{action}] FINISHED in #{sprintf "%.5f",d} seconds"
69
67
  end
70
68
  rescue => e
71
- puts "something bad happend: #{e.class} #{e.message}"
72
- end
73
-
74
- def uses_threads?
75
- @use_threads
76
- end
77
-
78
- def finalize_threads(threads)
79
- threads.each { |t| t.join }
80
- end
81
-
82
- def log(where, what, index, message)
83
- puts "[#{where}] #{what} #{' ' * (index * 4)}#{message}"
69
+ puts "something bad happened unexpectedly: #{e.class} => #{e.message}"
84
70
  end
85
71
  end
86
72
  end
@@ -1,6 +1,7 @@
1
1
  require 'erubis'
2
2
  require 'net/ssh'
3
3
  require 'socket'
4
+ require 'timeout'
4
5
 
5
6
  module Hetzner
6
7
  class Bootstrap
@@ -16,13 +17,15 @@ module Hetzner
16
17
  attr_accessor :post_install
17
18
  attr_accessor :public_keys
18
19
  attr_accessor :bootstrap_cmd
20
+ attr_accessor :logger
19
21
 
20
22
  def initialize(options = {})
21
23
  @rescue_os = 'linux'
22
24
  @rescue_os_bit = '64'
23
25
  @retries = 0
24
26
  @bootstrap_cmd = '/root/.oldroot/nfs/install/installimage -a -c /tmp/template'
25
-
27
+ @login = 'root'
28
+
26
29
  if tmpl = options.delete(:template)
27
30
  @template = Template.new tmpl
28
31
  else
@@ -38,19 +41,19 @@ module Hetzner
38
41
  result = @api.enable_rescue! @ip, @rescue_os, @rescue_os_bit
39
42
 
40
43
  if result.success? && result['rescue']
41
- @login = 'root'
42
44
  @password = result['rescue']['password']
43
45
  reset_retries
44
- puts "IP: #{ip} => password: #{@password}"
46
+ logger.info "IP: #{ip} => password: #{@password}"
45
47
  elsif @retries > 3
48
+ logger.error "rescue system could not be activated"
46
49
  raise CantActivateRescueSystemError, result
47
50
  else
48
51
  @retries += 1
49
52
 
50
- puts "problem while trying to activate rescue system (retries: #{@retries})"
53
+ logger.warn "problem while trying to activate rescue system (retries: #{@retries})"
51
54
  @api.disable_rescue! @ip
52
55
 
53
- sleep @retries * 5 # => 5, 10, 15s
56
+ rolling_sleep
54
57
  enable_rescue_mode options
55
58
  end
56
59
  end
@@ -60,37 +63,58 @@ module Hetzner
60
63
 
61
64
  if result.success?
62
65
  reset_retries
63
- sleep 15
64
66
  elsif @retries > 3
67
+ logger.error "resetting through webservice failed."
65
68
  raise CantResetSystemError, result
66
69
  else
67
70
  @retries += 1
71
+ logger.warn "problem while trying to reset/reboot system (retries: #{@retries})"
68
72
  rolling_sleep
69
- puts "problem while trying to reset/reboot system (retries: #{@retries})"
70
73
  reset options
71
74
  end
72
75
  end
73
76
 
74
- def wait_for_ssh(options = {})
75
- ssh_port_probe = TCPSocket.new @ip, 22
76
- return if IO.select([ssh_port_probe], nil, nil, 5)
77
+ def port_open? ip, port
78
+ ssh_port_probe = TCPSocket.new ip, port
79
+ IO.select([ssh_port_probe], nil, nil, 2)
80
+ ssh_port_probe.close
81
+ true
82
+ end
77
83
 
84
+ def wait_for_ssh_down(options = {})
85
+ loop do
86
+ Timeout::timeout(3) do
87
+ if port_open? @ip, 22
88
+ logger.debug "SSH UP"
89
+ sleep 10
90
+ else
91
+ raise Errno::ECONNREFUSED
92
+ end
93
+ end
94
+ sleep 2
95
+ end
96
+ rescue Timeout::Error
97
+ sleep 2
98
+ retry
78
99
  rescue Errno::ECONNREFUSED
79
- @retries += 1
80
- print "."
81
- STDOUT.flush
100
+ logger.debug "SHH DOWN"
101
+ end
82
102
 
83
- if @retries > 20
84
- raise CantSshAfterResetError
85
- else
86
- rolling_sleep
87
- wait_for_ssh options
103
+ def wait_for_ssh_up(options = {})
104
+ loop do
105
+ Timeout::timeout(4) do
106
+ if port_open? @ip, 22
107
+ logger.debug "SSH UP"
108
+ return true
109
+ else
110
+ raise Errno::ECONNREFUSED
111
+ end
112
+ end
88
113
  end
89
- rescue => e
90
- puts "Exception: #{e.class} #{e.message}"
91
- ensure
92
- puts ""
93
- ssh_port_probe && ssh_port_probe.close
114
+ rescue Errno::ECONNREFUSED, Timeout::Error
115
+ logger.debug "SSH DOWN"
116
+ sleep 2
117
+ retry
94
118
  end
95
119
 
96
120
  def installimage(options = {})
@@ -98,11 +122,18 @@ module Hetzner
98
122
 
99
123
  Net::SSH.start(@ip, @login, :password => @password) do |ssh|
100
124
  ssh.exec!("echo \"#{template}\" > /tmp/template")
101
- puts "remote executing: #{@bootstrap_cmd}"
125
+ logger.info "remote executing: #{@bootstrap_cmd}"
102
126
  output = ssh.exec!(@bootstrap_cmd)
103
- puts output
127
+ logger.info output
128
+ end
129
+ rescue Net::SSH::HostKeyMismatch => e
130
+ e.remember_host!
131
+ retry
132
+ end
133
+
134
+ def reboot(options = {})
135
+ Net::SSH.start(@ip, @login, :password => @password) do |ssh|
104
136
  ssh.exec!("reboot")
105
- sleep 4
106
137
  end
107
138
  rescue Net::SSH::HostKeyMismatch => e
108
139
  e.remember_host!
@@ -139,8 +170,8 @@ module Hetzner
139
170
  def post_install(options = {})
140
171
  return unless @post_install
141
172
  post_install = render_post_install
142
- puts "executing:\n #{post_install}"
143
- puts `#{post_install}`
173
+ logger.info "executing post_install:\n #{post_install}"
174
+ logger.info `#{post_install}`
144
175
  end
145
176
 
146
177
  def render_template
@@ -165,8 +196,13 @@ module Hetzner
165
196
  return eruby.result(params)
166
197
  end
167
198
 
168
- def use_api(api)
169
- @api = api
199
+ def use_api(api_obj)
200
+ @api = api_obj
201
+ end
202
+
203
+ def use_logger(logger_obj)
204
+ @logger = logger_obj
205
+ @logger.formatter = default_log_formatter
170
206
  end
171
207
 
172
208
  def reset_retries
@@ -177,6 +213,13 @@ module Hetzner
177
213
  sleep @retries * @retries * 3 + 1 # => 1, 4, 13, 28, 49, 76, 109, 148, 193, 244, 301, 364 ... seconds
178
214
  end
179
215
 
216
+ def default_log_formatter
217
+ proc do |severity, datetime, progname, msg|
218
+ caller[4]=~/`(.*?)'/
219
+ "[#{datetime.strftime "%H:%M:%S"}][#{sprintf "%-15s", ip}][#{$1}] #{msg}\n"
220
+ end
221
+ end
222
+
180
223
  class NoTemplateProvidedError < ArgumentError; end
181
224
  class CantActivateRescueSystemError < StandardError; end
182
225
  class CantResetSystemError < StandardError; end
@@ -1,5 +1,5 @@
1
1
  module Hetzner
2
2
  class Bootstrap
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hetzner-bootstrap
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Roland Moriz
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-06 00:00:00 +01:00
18
+ date: 2011-02-11 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -89,7 +89,7 @@ files:
89
89
  - .gitignore
90
90
  - Gemfile
91
91
  - LICENSE
92
- - README
92
+ - README.rdoc
93
93
  - Rakefile
94
94
  - example.rb
95
95
  - hetzner-bootstrap.gemspec
data/README DELETED
@@ -1,9 +0,0 @@
1
- hetzner-bootstrap allows you to bootstrap a provisioned EQ Server from hetzner.de
2
-
3
- Requirements
4
-
5
- - get a webservice login (robots.your-server.de)
6
- - the ip address of the shipped systems
7
-
8
- see example.rb for usage
9
-