hetzner-bootstrap-coreos 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +93 -0
- data/Rakefile +2 -0
- data/example.rb +33 -0
- data/hetzner-bootstrap-coreos.gemspec +26 -0
- data/lib/hetzner/bootstrap/coreos/cloud_config.rb +21 -0
- data/lib/hetzner/bootstrap/coreos/target.rb +241 -0
- data/lib/hetzner/bootstrap/coreos/version.rb +7 -0
- data/lib/hetzner-bootstrap-coreos.rb +71 -0
- data/spec/hetzner_bootstrap_coreos_spec.rb +49 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d07dc6e9373f36a16bc6da8782dee0d1c9b958d2
|
4
|
+
data.tar.gz: 0fc042ce7cfc437391037d4c6bc3e1b66ffb587e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e4a4e45ee787118c7094771f40c7a9bc4b5897d7e621161b6891a022f817be95b9ac99e20a34c659a66ce9e4426f08c4cac55fb90d8a1804c60448f28a5f843a
|
7
|
+
data.tar.gz: 812962458371ed6c2d600120e8e38303a3b7379de10a5d6244773fd5c03cf9b1db9b6ae36af5524a79fb722180f8c8b41bd02812d22c21bd401a4036e8c5dca5
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Moriz GmbH, Roland Moriz, http://moriz.de/
|
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.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# hetzner-bootstrap-coreos
|
2
|
+
|
3
|
+
This gem allows you to bootstrap CoreOS on a Hetzner root server.
|
4
|
+
|
5
|
+
[](http://badge.fury.io/rb/hetzner-bootstrap-coreos)
|
6
|
+
|
7
|
+
## What it does:
|
8
|
+
/3.
|
9
|
+
In our case we are running a large [CoreOS](https://coreos.com) cluster and which uses bare metal servers hosted at Hetzner. This Ruby gem helps to fully automate the provisioning of CoreOS on a Hetzner root server.
|
10
|
+
|
11
|
+
Warning: All existing data on the system will be lost!
|
12
|
+
|
13
|
+
## Requirements:
|
14
|
+
|
15
|
+
First of all retrieve your API login credentials from the Hetzner admin interface at [https://robot.your-server.de](https://robot.your-server.de). Additionally you need the IP address of the shipped system.
|
16
|
+
|
17
|
+
## Implemented steps:
|
18
|
+
|
19
|
+
1. Enable Rescue Mode (using Hetzner's webservice)
|
20
|
+
2. Resetting the System to boot into rescue mode (using Hetzner's webservice)
|
21
|
+
3. Log into the rescue system, write your cloud config file and launch the installation
|
22
|
+
4. Reboot
|
23
|
+
5. Verify installation (very basic check but can be overwritten)
|
24
|
+
6. Copy your local ssh public-key into root's .authorized_keys
|
25
|
+
7. Adds the generated server key into your .know_hosts file
|
26
|
+
8. Execute post_install hooks (optional)
|
27
|
+
|
28
|
+
## Example:
|
29
|
+
|
30
|
+
**see example.rb file for usage!**
|
31
|
+
|
32
|
+
Warning: All existing data on the system will be lost!
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
|
36
|
+
#!/usr/bin/env ruby
|
37
|
+
require "rubygems"
|
38
|
+
require "hetzner-bootstrap-coreos"
|
39
|
+
|
40
|
+
# Retrieve your API login credentials from the Hetzner admin interface
|
41
|
+
# at https://robot.your-server.de and assign the appropriate environment
|
42
|
+
# variables ENV['ROBOT_USER'] and ENV['ROBOT_PASSWORD']
|
43
|
+
|
44
|
+
bs = Hetzner::Bootstrap::CoreOS.new(
|
45
|
+
:api => Hetzner::API.new(ENV['ROBOT_USER'], ENV['ROBOT_PASSWORD'])
|
46
|
+
)
|
47
|
+
|
48
|
+
# Main configuration (cloud-config)
|
49
|
+
cloud_config = <<EOT
|
50
|
+
hostname: <%= hostname %>
|
51
|
+
ssh_authorized_keys:
|
52
|
+
- <%= public_keys %>
|
53
|
+
EOT
|
54
|
+
|
55
|
+
# The post_install hook is the right place to launch further tasks (e.g.
|
56
|
+
# software installation, system provisioning etc.)
|
57
|
+
post_install = <<EOT
|
58
|
+
# TODO
|
59
|
+
EOT
|
60
|
+
|
61
|
+
bs << { :ip => "1.2.3.4",
|
62
|
+
:cloud_config => cloud_config,
|
63
|
+
:hostname => 'artemis.massive-insights.com',
|
64
|
+
:public_keys => "~/.ssh/id_dsa.pub",
|
65
|
+
:post_install => post_install
|
66
|
+
}
|
67
|
+
|
68
|
+
bs.bootstrap!
|
69
|
+
|
70
|
+
```
|
71
|
+
|
72
|
+
Installation:
|
73
|
+
-------------
|
74
|
+
|
75
|
+
**gem install hetzner-bootstrap-coreos**
|
76
|
+
|
77
|
+
Warnings:
|
78
|
+
---------
|
79
|
+
|
80
|
+
* All existing data on the system will be wiped on bootstrap!
|
81
|
+
* This is not an official Hetzner AG project.
|
82
|
+
* The gem and the author are not related to Hetzner AG!
|
83
|
+
|
84
|
+
**Use at your very own risk. Satisfaction is NOT guaranteed.**
|
85
|
+
|
86
|
+
## Thank you greeting
|
87
|
+
|
88
|
+
This Ruby gem is inspired by the [hetzner-bootstrap](https://github.com/rmoriz/hetzner-bootstrap) gem and requires the underlying wrapper for the Hetzner server management API [hetzner-api](https://github.com/rmoriz/hetzner-api). Thus I want to thank [Roland Moriz](https://roland.io/developer) a lot for his great work!
|
89
|
+
|
90
|
+
Copyright
|
91
|
+
---------
|
92
|
+
|
93
|
+
Copyright © 2014 Christoph Pilka ([Asconix Systems AS](https://www.asconix.com)
|
data/Rakefile
ADDED
data/example.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "hetzner-bootstrap-coreos"
|
4
|
+
|
5
|
+
# Retrieve your API login credentials from the Hetzner admin interface
|
6
|
+
# at https://robot.your-server.de and assign the appropriate environment
|
7
|
+
# variables ENV['ROBOT_USER'] and ENV['ROBOT_PASSWORD']
|
8
|
+
|
9
|
+
bs = Hetzner::Bootstrap::CoreOS.new(
|
10
|
+
:api => Hetzner::API.new(ENV['ROBOT_USER'], ENV['ROBOT_PASSWORD'])
|
11
|
+
)
|
12
|
+
|
13
|
+
# Main configuration (cloud-config)
|
14
|
+
cloud_config = <<EOT
|
15
|
+
hostname: <%= hostname %>
|
16
|
+
ssh_authorized_keys:
|
17
|
+
- <%= public_keys %>
|
18
|
+
EOT
|
19
|
+
|
20
|
+
# The post_install hook is the right place to launch further tasks (e.g.
|
21
|
+
# software installation, system provisioning etc.)
|
22
|
+
post_install = <<EOT
|
23
|
+
# TODO
|
24
|
+
EOT
|
25
|
+
|
26
|
+
bs << { :ip => "1.2.3.4",
|
27
|
+
:cloud_config => cloud_config,
|
28
|
+
:hostname => 'artemis.massive-insights.com',
|
29
|
+
:public_keys => "~/.ssh/id_dsa.pub",
|
30
|
+
:post_install => post_install
|
31
|
+
}
|
32
|
+
|
33
|
+
bs.bootstrap!
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "hetzner/bootstrap/coreos/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "hetzner-bootstrap-coreos"
|
7
|
+
s.version = Hetzner::Bootstrap::CoreOS::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Christoph Pilka"]
|
10
|
+
s.email = ["c.pilka@asconix.com"]
|
11
|
+
s.homepage = "http://www.asconix.com"
|
12
|
+
s.summary = %q{Bootstrapping of the root servers at Hetzner with CoreOS}
|
13
|
+
s.description = %q{Bootstrapping of the root servers at Hetzner with CoreOS}
|
14
|
+
|
15
|
+
s.add_dependency 'hetzner-api', '>= 1.1.0'
|
16
|
+
s.add_dependency 'net-ssh', '>= 2.6.0'
|
17
|
+
s.add_dependency 'erubis', '>= 2.7.0'
|
18
|
+
|
19
|
+
s.add_development_dependency "rspec", ">= 2.13.0"
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.require_paths = ["lib"]
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Hetzner
|
2
|
+
class Bootstrap
|
3
|
+
class CoreOS
|
4
|
+
class CloudConfig
|
5
|
+
attr_accessor :raw_cloud_config
|
6
|
+
|
7
|
+
def initialize(param)
|
8
|
+
if param.is_a? Hetzner::Bootstrap::CoreOS::CloudConfig
|
9
|
+
return param
|
10
|
+
elsif param.is_a? String
|
11
|
+
@raw_cloud_config = param
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@raw_clooud_config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
module Hetzner
|
7
|
+
class Bootstrap
|
8
|
+
class CoreOS
|
9
|
+
class Target
|
10
|
+
attr_accessor :ip
|
11
|
+
attr_accessor :login
|
12
|
+
attr_accessor :password
|
13
|
+
attr_accessor :cloud_config
|
14
|
+
attr_accessor :rescue_os
|
15
|
+
attr_accessor :rescue_os_bit
|
16
|
+
attr_accessor :actions
|
17
|
+
attr_accessor :hostname
|
18
|
+
attr_accessor :post_install
|
19
|
+
attr_accessor :post_install_remote
|
20
|
+
attr_accessor :public_keys
|
21
|
+
attr_accessor :bootstrap_cmd
|
22
|
+
attr_accessor :logger
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
@rescue_os = 'linux'
|
26
|
+
@rescue_os_bit = '64'
|
27
|
+
@retries = 0
|
28
|
+
@bootstrap_cmd = 'export TERM=xterm; /tmp/coreos-install -d /dev/sda -C stable -c /tmp/cloud-config.yaml'
|
29
|
+
@login = 'root'
|
30
|
+
|
31
|
+
if cc = options.delete(:cloud_config)
|
32
|
+
@cloud_config = CloudConfig.new cc
|
33
|
+
else
|
34
|
+
raise NoCloudConfigProvidedError.new 'No cloud config file provided.'
|
35
|
+
end
|
36
|
+
|
37
|
+
options.each_pair do |k,v|
|
38
|
+
self.send("#{k}=", v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def enable_rescue_mode(options = {})
|
43
|
+
result = @api.enable_rescue! @ip, @rescue_os, @rescue_os_bit
|
44
|
+
|
45
|
+
if result.success? && result['rescue']
|
46
|
+
@password = result['rescue']['password']
|
47
|
+
reset_retries
|
48
|
+
logger.info "IP: #{ip} => password: #{@password}"
|
49
|
+
elsif @retries > 3
|
50
|
+
logger.error "Rescue system could not be activated"
|
51
|
+
raise CantActivateRescueSystemError, result
|
52
|
+
else
|
53
|
+
@retries += 1
|
54
|
+
|
55
|
+
logger.warn "Problem while trying to activate rescue system (retries: #{@retries})"
|
56
|
+
@api.disable_rescue! @ip
|
57
|
+
|
58
|
+
rolling_sleep
|
59
|
+
enable_rescue_mode options
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset(options = {})
|
64
|
+
result = @api.reset! @ip, :hw
|
65
|
+
|
66
|
+
if result.success?
|
67
|
+
reset_retries
|
68
|
+
elsif @retries > 3
|
69
|
+
logger.error "Resetting through web service failed."
|
70
|
+
raise CantResetSystemError, result
|
71
|
+
else
|
72
|
+
@retries += 1
|
73
|
+
logger.warn "Problem while trying to reset/reboot system (retries: #{@retries})"
|
74
|
+
rolling_sleep
|
75
|
+
reset options
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def port_open? ip, port
|
80
|
+
ssh_port_probe = TCPSocket.new ip, port
|
81
|
+
IO.select([ssh_port_probe], nil, nil, 2)
|
82
|
+
ssh_port_probe.close
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def wait_for_ssh_down(options = {})
|
87
|
+
loop do
|
88
|
+
sleep 2
|
89
|
+
Timeout::timeout(4) do
|
90
|
+
if port_open? @ip, 22
|
91
|
+
logger.debug "SSH UP"
|
92
|
+
else
|
93
|
+
raise Errno::ECONNREFUSED
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue Timeout::Error, Errno::ECONNREFUSED
|
98
|
+
logger.debug "SSH down"
|
99
|
+
end
|
100
|
+
|
101
|
+
def wait_for_ssh_up(options = {})
|
102
|
+
loop do
|
103
|
+
Timeout::timeout(4) do
|
104
|
+
if port_open? @ip, 22
|
105
|
+
logger.debug "SSH up"
|
106
|
+
return true
|
107
|
+
else
|
108
|
+
raise Errno::ECONNREFUSED
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
rescue Errno::ECONNREFUSED, Timeout::Error
|
113
|
+
logger.debug "SSH down"
|
114
|
+
sleep 2
|
115
|
+
retry
|
116
|
+
end
|
117
|
+
|
118
|
+
def installimage(options = {})
|
119
|
+
cloud_config = render_cloud_config
|
120
|
+
|
121
|
+
remote do |ssh|
|
122
|
+
ssh.exec! "echo \"#{cloud_config}\" > /tmp/cloud-config.yaml"
|
123
|
+
logger.info "Remote executing: #{@bootstrap_cmd}"
|
124
|
+
output = ssh.exec!(@bootstrap_cmd)
|
125
|
+
logger.info output
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def reboot(options = {})
|
130
|
+
remote do |ssh|
|
131
|
+
ssh.exec!("reboot")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def verify_installation(options = {})
|
136
|
+
remote do |ssh|
|
137
|
+
working_hostname = ssh.exec!("cat /etc/hostname")
|
138
|
+
unless @hostname == working_hostname.chomp
|
139
|
+
raise InstallationError, "Hostnames do not match: assumed #{@hostname} but received #{working_hostname}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def update_local_known_hosts(options = {})
|
145
|
+
remote(:paranoid => true) do |ssh|
|
146
|
+
# dummy
|
147
|
+
end
|
148
|
+
rescue Net::SSH::HostKeyMismatch => e
|
149
|
+
e.remember_host!
|
150
|
+
logger.info "Remote host key added to local ~/.ssh/known_hosts file."
|
151
|
+
end
|
152
|
+
|
153
|
+
def post_install(options = {})
|
154
|
+
return unless @post_install
|
155
|
+
|
156
|
+
post_install = render_post_install
|
157
|
+
logger.info "Executing post_install:\n #{post_install}"
|
158
|
+
|
159
|
+
output = local do
|
160
|
+
`#{post_install}`
|
161
|
+
end
|
162
|
+
|
163
|
+
logger.info output
|
164
|
+
end
|
165
|
+
|
166
|
+
def post_install_remote(options = {})
|
167
|
+
remote do |ssh|
|
168
|
+
@post_install_remote.split("\n").each do |cmd|
|
169
|
+
cmd.chomp!
|
170
|
+
logger.info "executing #{cmd}"
|
171
|
+
ssh.exec!(cmd)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def render_cloud_config
|
177
|
+
eruby = Erubis::Eruby.new @cloud_config.to_s
|
178
|
+
|
179
|
+
params = {}
|
180
|
+
params[:hostname] = @hostname
|
181
|
+
params[:ip] = @ip
|
182
|
+
|
183
|
+
return eruby.result(params)
|
184
|
+
end
|
185
|
+
|
186
|
+
def render_post_install
|
187
|
+
eruby = Erubis::Eruby.new @post_install.to_s
|
188
|
+
|
189
|
+
params = {}
|
190
|
+
params[:hostname] = @hostname
|
191
|
+
params[:ip] = @ip
|
192
|
+
params[:login] = @login
|
193
|
+
params[:password] = @password
|
194
|
+
|
195
|
+
return eruby.result(params)
|
196
|
+
end
|
197
|
+
|
198
|
+
def use_api(api_obj)
|
199
|
+
@api = api_obj
|
200
|
+
end
|
201
|
+
|
202
|
+
def use_logger(logger_obj)
|
203
|
+
@logger = logger_obj
|
204
|
+
@logger.formatter = default_log_formatter
|
205
|
+
end
|
206
|
+
|
207
|
+
def remote(options = {}, &block)
|
208
|
+
default = { :paranoid => false, :password => @password }
|
209
|
+
default.merge! options
|
210
|
+
Net::SSH.start(@ip, @login, default) do |ssh|
|
211
|
+
block.call ssh
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def local(&block)
|
216
|
+
block.call
|
217
|
+
end
|
218
|
+
|
219
|
+
def reset_retries
|
220
|
+
@retries = 0
|
221
|
+
end
|
222
|
+
|
223
|
+
def rolling_sleep
|
224
|
+
sleep @retries * @retries * 3 + 1 # => 1, 4, 13, 28, 49, 76, 109, 148, 193, 244, 301, 364 ... seconds
|
225
|
+
end
|
226
|
+
|
227
|
+
def default_log_formatter
|
228
|
+
proc do |severity, datetime, progname, msg|
|
229
|
+
caller[4]=~/`(.*?)'/
|
230
|
+
"[#{datetime.strftime "%H:%M:%S"}][#{sprintf "%-15s", ip}][#{$1}] #{msg}\n"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class NoCloudConfigProvidedError < ArgumentError; end
|
235
|
+
class CantActivateRescueSystemError < StandardError; end
|
236
|
+
class CantResetSystemError < StandardError; end
|
237
|
+
class InstallationError < StandardError; end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'hetzner-api'
|
5
|
+
require 'hetzner/bootstrap/coreos/version'
|
6
|
+
require 'hetzner/bootstrap/coreos/target'
|
7
|
+
require 'hetzner/bootstrap/coreos/cloud_config'
|
8
|
+
|
9
|
+
module Hetzner
|
10
|
+
class Bootstrap
|
11
|
+
class CoreOS
|
12
|
+
attr_accessor :targets
|
13
|
+
attr_accessor :api
|
14
|
+
attr_accessor :actions
|
15
|
+
attr_accessor :logger
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@targets = []
|
19
|
+
@actions = %w(enable_rescue_mode
|
20
|
+
reset
|
21
|
+
wait_for_ssh_down
|
22
|
+
wait_for_ssh_up
|
23
|
+
installimage
|
24
|
+
reboot
|
25
|
+
wait_for_ssh_down
|
26
|
+
wait_for_ssh_up
|
27
|
+
verify_installation
|
28
|
+
update_local_known_hosts
|
29
|
+
post_install
|
30
|
+
post_install_remote)
|
31
|
+
@api = options[:api]
|
32
|
+
@logger = options[:logger] || Logger.new(STDOUT)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_target(param)
|
36
|
+
if param.is_a? Hetzner::Bootstrap::CoreOS::Target
|
37
|
+
@targets << param
|
38
|
+
else
|
39
|
+
@targets << (Hetzner::Bootstrap::CoreOS::Target.new param)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(param)
|
44
|
+
add_target param
|
45
|
+
end
|
46
|
+
|
47
|
+
def bootstrap!(options = {})
|
48
|
+
@targets.each do |target|
|
49
|
+
target.use_api @api
|
50
|
+
target.use_logger @logger
|
51
|
+
bootstrap_one_target! target
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def bootstrap_one_target!(target)
|
56
|
+
actions = (target.actions || @actions)
|
57
|
+
actions.each_with_index do |action, index|
|
58
|
+
loghack = "\b" * 24 # remove: "[bootstrap_one_target!] ".length
|
59
|
+
target.logger.info "#{loghack}[#{action}] #{sprintf "%-20s", "START"}"
|
60
|
+
d = Benchmark.realtime do
|
61
|
+
target.send action
|
62
|
+
end
|
63
|
+
target.logger.info "#{loghack}[#{action}] FINISHED in #{sprintf "%.5f",d} seconds"
|
64
|
+
end
|
65
|
+
rescue => e
|
66
|
+
puts "Something bad happened unexpectedly: #{e.class} => #{e.message}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'hetzner-api'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe "Bootstrap" do
|
5
|
+
before(:all) do
|
6
|
+
@api = Hetzner::API.new API_USERNAME, API_PASSWORD
|
7
|
+
@bootstrap = Hetzner::Bootstrap::CoreOS.new :api => @api
|
8
|
+
end
|
9
|
+
|
10
|
+
context "add target" do
|
11
|
+
|
12
|
+
it "should be able to add a server to operate on" do
|
13
|
+
@bootstrap.add_target proper_target
|
14
|
+
@bootstrap.targets.should have(1).target
|
15
|
+
@bootstrap.targets.first.should be_instance_of Hetzner::Bootstrap::CoreOS::Target
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have the default cloud config file if none is specified" do
|
19
|
+
@bootstrap.add_target proper_target
|
20
|
+
@bootstrap.targets.first.cloud_config.should be_instance_of Hetzner::Bootstrap::CoreOS::CloudConfig
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise an NoCloudConfigProvidedError when no cloud config option provided" do
|
24
|
+
lambda {
|
25
|
+
@bootstrap.add_target improper_target_without_cloud_condfig
|
26
|
+
}.should raise_error(Hetzner::Bootstrap::CoreOS::Target::NoCloudConfigProvidedError)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def proper_target
|
32
|
+
return {
|
33
|
+
:ip => "1.2.3.4",
|
34
|
+
:login => "root",
|
35
|
+
:password => "verysecret",
|
36
|
+
:rescue_os => "linux",
|
37
|
+
:rescue_os_bit => "64",
|
38
|
+
:cloud_config => default_cloud_config
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def improper_target_without_cloud_config
|
43
|
+
proper_target.select { |k,v| k != :cloud_config }
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_cloud_config
|
47
|
+
"foobar"
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hetzner-bootstrap-coreos
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Pilka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hetzner-api
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.6.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.6.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: erubis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.7.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.13.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.13.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Bootstrapping of the root servers at Hetzner with CoreOS
|
84
|
+
email:
|
85
|
+
- c.pilka@asconix.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- example.rb
|
96
|
+
- hetzner-bootstrap-coreos.gemspec
|
97
|
+
- lib/hetzner-bootstrap-coreos.rb
|
98
|
+
- lib/hetzner/bootstrap/coreos/cloud_config.rb
|
99
|
+
- lib/hetzner/bootstrap/coreos/target.rb
|
100
|
+
- lib/hetzner/bootstrap/coreos/version.rb
|
101
|
+
- spec/hetzner_bootstrap_coreos_spec.rb
|
102
|
+
homepage: http://www.asconix.com
|
103
|
+
licenses: []
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.2.2
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Bootstrapping of the root servers at Hetzner with CoreOS
|
125
|
+
test_files:
|
126
|
+
- spec/hetzner_bootstrap_coreos_spec.rb
|