hazetug 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +188 -0
- data/Rakefile +1 -0
- data/hazetug.gemspec +28 -0
- data/lib/hazetug/cli/action.rb +30 -0
- data/lib/hazetug/cli/bootstrap.rb +88 -0
- data/lib/hazetug/cli.rb +74 -0
- data/lib/hazetug/compute.rb +80 -0
- data/lib/hazetug/config.rb +26 -0
- data/lib/hazetug/haze/cloud_server.rb +71 -0
- data/lib/hazetug/haze/digital_ocean.rb +71 -0
- data/lib/hazetug/haze/linode.rb +66 -0
- data/lib/hazetug/haze.rb +81 -0
- data/lib/hazetug/hazetug.rb +31 -0
- data/lib/hazetug/task.rb +53 -0
- data/lib/hazetug/tug/knife.rb +97 -0
- data/lib/hazetug/tug.rb +85 -0
- data/lib/hazetug/ui.rb +75 -0
- data/lib/hazetug/version.rb +3 -0
- data/lib/hazetug.rb +4 -0
- metadata +164 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a5a2351211bc68264c5fe9b4780278bbbe5f419a
|
4
|
+
data.tar.gz: f1ecdb92eb86d8978754fb21da5bb0dcd38de525
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6ca974f9bc299115e6749def4b8b847fbdb8cbc57bc5570c6fcf54450617d5c0faffcb32600021bfba8ac25d70fe3cf3d75d6ac2e41a07fbe12876e3295f16a
|
7
|
+
data.tar.gz: 4f49bcae8fc1965fe9e35c153c69b51a4ddfa2d9228e6d37adfb5fae71824afd739f346ad53d6a6f0afee4a7a343e0b0841bbc7dd3c1161af369c78a8cabe40c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Denis Barishev
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# Hazetug
|
2
|
+
|
3
|
+
Cloud provisioner and bootstrapper for DigitalOcean and Linode.
|
4
|
+
Hazetug uses [fog cloud library](http://fog.io) to be able to easily append other cloud computes and *tugs* (bootstraps) hosts using:
|
5
|
+
|
6
|
+
* Knife bootstrap.
|
7
|
+
|
8
|
+
## Options
|
9
|
+
|
10
|
+
### Cloud Computes specific options
|
11
|
+
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<td><b>Option</b></td>
|
15
|
+
<td><b>Description</b></td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td><b><i>name</i></b></td>
|
19
|
+
<td>Name of a host to provision. Random names are possible using %rand(x)% macro.</td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td><b><i>location</i></b></td>
|
23
|
+
<td>Location in the cloud compute, namely data center.</td>
|
24
|
+
</tr>
|
25
|
+
<tr>
|
26
|
+
<td><b><i>flavor</i></b></td>
|
27
|
+
<td>Flavor of a provisioned host. Flavors are recognized by memory, use: 512mb, 4gb, etc to choose the proper one.</td>
|
28
|
+
</tr>
|
29
|
+
<tr>
|
30
|
+
<td><b><i>image</i></b></td>
|
31
|
+
<td>Distribution or image which is used for provisioning. Examples: ubuntu-12.04-x32, arch-linux-2014.14. If OS architecture is not specified then x64 is assumed.</td>
|
32
|
+
</tr>
|
33
|
+
</table>
|
34
|
+
|
35
|
+
### SSH Specific
|
36
|
+
|
37
|
+
<table>
|
38
|
+
<tr>
|
39
|
+
<td><b>Option</b></td>
|
40
|
+
<td><b>Description</b></td>
|
41
|
+
<td><b>Default value</b></td>
|
42
|
+
</tr>
|
43
|
+
<tr>
|
44
|
+
<td><b><i>ssh_user</i></b></td>
|
45
|
+
<td>User used to during provisioning and for connecting via ssh.</td>
|
46
|
+
<td><i>root</i><td>
|
47
|
+
</tr>
|
48
|
+
<tr>
|
49
|
+
<td><b><i>ssh_password</i></b></td>
|
50
|
+
<td>Password for a provisioned node. Evaluated randomly for some computes.</td>
|
51
|
+
<td></td>
|
52
|
+
</tr>
|
53
|
+
<tr>
|
54
|
+
<td><b><i>ssh_port</i></b></td>
|
55
|
+
<td>Port used for ssh connection.</td>
|
56
|
+
<td><i>22</i><td>
|
57
|
+
</tr>
|
58
|
+
<tr>
|
59
|
+
<td><b><i>host_key_verify</i></b></td>
|
60
|
+
<td>Verifies host key, set to true to enable verification.</td>
|
61
|
+
<td><i>false</i><td>
|
62
|
+
</tr>
|
63
|
+
</table>
|
64
|
+
|
65
|
+
## Knife tug
|
66
|
+
|
67
|
+
### Knife tug bootstrap options
|
68
|
+
|
69
|
+
<table>
|
70
|
+
<tr>
|
71
|
+
<td><b>Option</b></td>
|
72
|
+
<td><b>Description</b></td>
|
73
|
+
<td><b>Default value</b></td>
|
74
|
+
</tr>
|
75
|
+
<tr>
|
76
|
+
<td><b><i>chef_validation_key</i></b></td>
|
77
|
+
<td>Validation key used to authenticate new nodes in the Chef Server.</td>
|
78
|
+
<td><i>validation.pem</i><td>
|
79
|
+
</tr>
|
80
|
+
<tr>
|
81
|
+
<td><b><i>chef_environment</i></b></td>
|
82
|
+
<td>Chef Environment used during bootstrap</td>
|
83
|
+
<td></td>
|
84
|
+
</tr>
|
85
|
+
<tr>
|
86
|
+
<td><b><i>chef_server_url</i></b></td>
|
87
|
+
<td>URL of the Chef Serer.</td>
|
88
|
+
<td></td>
|
89
|
+
</tr>
|
90
|
+
</table>
|
91
|
+
|
92
|
+
## Installation
|
93
|
+
|
94
|
+
Add this line to your application's Gemfile:
|
95
|
+
|
96
|
+
gem 'hazetug'
|
97
|
+
|
98
|
+
And then execute:
|
99
|
+
|
100
|
+
$ bundle
|
101
|
+
|
102
|
+
Or install it yourself as:
|
103
|
+
|
104
|
+
$ gem install hazetug
|
105
|
+
|
106
|
+
## Usage
|
107
|
+
|
108
|
+
### Configuration file
|
109
|
+
|
110
|
+
Create *~/.hazetug* configuration file, with the content like:
|
111
|
+
|
112
|
+
```yaml
|
113
|
+
default:
|
114
|
+
linode_api_key: YOUR_LINODE_API_KEY
|
115
|
+
linode_ssh_keys:
|
116
|
+
- ~/.ssh/linode.pem (Change with your path, it also might be missing)
|
117
|
+
digitalocean_api_key: DIGITALOCEAN_API_KEY
|
118
|
+
digitalocean_client_id: DIGITALOCEAN_CLIENT_ID
|
119
|
+
digitalocean_ssh_keys:
|
120
|
+
- ~/.ssh/digitalocean.pem (Change with your path)
|
121
|
+
```
|
122
|
+
|
123
|
+
### Tasks
|
124
|
+
|
125
|
+
Hazetug bootstrap task file is yaml file as well, it consists of two sections **global** and **bootstrap**. Global section sets default variables used by hazetug, each bootstraped host from bootstrap list sets variables specific to it thus redefining global defaults. Let's have a look at a sample task file:
|
126
|
+
|
127
|
+
```
|
128
|
+
chef_server_url: 'https://mychefserver.uri'
|
129
|
+
chef_environment: staging
|
130
|
+
chef_version: 11.14.6
|
131
|
+
ruby_version: ruby-2.1.2
|
132
|
+
ssh_password: my-password-on-api-nodes
|
133
|
+
bootstrap:
|
134
|
+
- name: api-testbox-%rand(6)%
|
135
|
+
number: 2
|
136
|
+
location: london
|
137
|
+
flavor: 2gb
|
138
|
+
image: ubuntu-14.04-x64
|
139
|
+
run_list: ["role[api]"]
|
140
|
+
```
|
141
|
+
|
142
|
+
From the example above we can see various variables used by hazetug they are common for all bootstrapped nodes, that's why it's reasonable to locate them in the global. However each variable has three layer hierarchy more details look into the [Variables Priority](README.md#variables-priority) section.
|
143
|
+
|
144
|
+
### Variables priority
|
145
|
+
|
146
|
+
Hazetug uses 3-level priority for flexible variable choosing. Priority in the ascending order is the following: variable from the global section -> variable set via command option -> variable in the bootstrap list entity.
|
147
|
+
All variables are merged using this 3-level priority.
|
148
|
+
|
149
|
+
|
150
|
+
### Command Line and Invocation
|
151
|
+
|
152
|
+
### Bootstrap using knife
|
153
|
+
|
154
|
+
Help for linode compute is given bellow:
|
155
|
+
|
156
|
+
```
|
157
|
+
NAME
|
158
|
+
knife - Bootstraps server using Knife
|
159
|
+
|
160
|
+
SYNOPSIS
|
161
|
+
hazetug.rb [global options] linode bootstrap knife [command options] task.yaml
|
162
|
+
|
163
|
+
COMMAND OPTIONS
|
164
|
+
-v, --variables=arg - Set variable or comma-seperated list of variables (var1_1=hello) (default: none)
|
165
|
+
-n, --number=arg - Set number of created nodes, value from yaml is honored (default: 1)
|
166
|
+
-c, --concurrency=arg - Set concurrency value, i.e. number of hosts bootstraped simultaneously (default: 1)
|
167
|
+
-b, --bootstrap=arg - Set path to knife bootstrap.erb file (default: bootstrap.erb)
|
168
|
+
```
|
169
|
+
|
170
|
+
All variables are passed to the bootstrap template and are available using the hazetug hash like - `hazetug[:variable_name]`. Amongst variables described here in the options sections, hazetug also passes useful variables such as ***compute_name***, ***public_ip_address***, ***private_ip_address*** if those are available.
|
171
|
+
|
172
|
+
#### Examples
|
173
|
+
|
174
|
+
* Provisioning and bootstrapping 5 nodes, each 3 of them will be processed simultaneously:
|
175
|
+
|
176
|
+
`hazetug digitalocean bootstrap knife -n 5 -c 3 -b api.erb api-task.yaml`
|
177
|
+
|
178
|
+
* Redefining validation_key and chef_version:
|
179
|
+
|
180
|
+
`hazetug digitalocean bootstrap knife -v validation_key=/tmp/validation.pem,chef_version=11.12.4 api.erb api-task.yaml`
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
1. Fork it ( http://github.com/dennybaa/hazetug/fork )
|
185
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
186
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
187
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
188
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/hazetug.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hazetug/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hazetug"
|
8
|
+
spec.version = Hazetug::VERSION
|
9
|
+
spec.authors = ["Denis Barishev"]
|
10
|
+
spec.email = ["denz@twiket.com"]
|
11
|
+
spec.summary = %q{Cloud provisoner tool}
|
12
|
+
spec.description = %q{Hazetug uses fog cloud library and he}
|
13
|
+
spec.homepage = "https://github.com/dennybaa/hazetug"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_dependency "psych"
|
24
|
+
spec.add_dependency "fog"
|
25
|
+
spec.add_dependency "chef", ">= 11.10.0"
|
26
|
+
spec.add_dependency "gli"
|
27
|
+
spec.add_dependency "agent"
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'hazetug/ui'
|
2
|
+
|
3
|
+
class Hazetug
|
4
|
+
class CLI
|
5
|
+
class Action
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def ui
|
9
|
+
Hazetug::UI.instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def pass(hash={})
|
13
|
+
@data = hash.dup
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def inherited(child)
|
19
|
+
action = child.name.split('::').last.downcase.to_sym
|
20
|
+
@actions ||= {}
|
21
|
+
@actions[action] = child
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](value)
|
25
|
+
@actions[value]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'agent'
|
2
|
+
require 'hazetug/cli/action'
|
3
|
+
require 'hazetug/tug'
|
4
|
+
require 'hazetug/task'
|
5
|
+
|
6
|
+
|
7
|
+
class Hazetug
|
8
|
+
class CLI
|
9
|
+
class Bootstrap < Action
|
10
|
+
def execute
|
11
|
+
concurrency = data[:opts][:concurrency].to_i || 1
|
12
|
+
queue = channel!(Object, concurrency)
|
13
|
+
waitgroup = Agent::WaitGroup.new
|
14
|
+
bootstrap_list do |haze, tug|
|
15
|
+
queue << nil; waitgroup.add(1)
|
16
|
+
block = method(:provision_and_bootstrap).to_proc
|
17
|
+
go!(haze, tug, queue, waitgroup, &block)
|
18
|
+
end
|
19
|
+
waitgroup.wait
|
20
|
+
end
|
21
|
+
|
22
|
+
def task
|
23
|
+
yaml_task = data[:args].shift
|
24
|
+
@task ||= Hazetug::Task.load_from_file(yaml_task)
|
25
|
+
end
|
26
|
+
|
27
|
+
def provision_and_bootstrap(haze, tug, channel, waitgroup)
|
28
|
+
haze.provision
|
29
|
+
tug.bootstrap({
|
30
|
+
args: data[:args],
|
31
|
+
opts: data[:opts],
|
32
|
+
gopts: data[:gopts]
|
33
|
+
})
|
34
|
+
rescue
|
35
|
+
# Exeception will be lost, since we run inside goproc,
|
36
|
+
# ie. as soon as waitgroup is empty all process exit.
|
37
|
+
puts $!.inspect
|
38
|
+
puts $@
|
39
|
+
ensure
|
40
|
+
waitgroup.done
|
41
|
+
channel.receive
|
42
|
+
end
|
43
|
+
|
44
|
+
def bootstrap_list(&block)
|
45
|
+
return if block.nil?
|
46
|
+
task.hosts_to_bootstrap(env_split) do |conf|
|
47
|
+
num = conf[:number] || data[:opts][:number].to_i || 1
|
48
|
+
if convert_rand_name(conf[:name]) == conf[:name] && num > 1
|
49
|
+
ui.fatal "Can't bootstrap several hosts with the same name"
|
50
|
+
raise ArgumentError, "%rand(x)% expected"
|
51
|
+
end
|
52
|
+
(1..num).each do
|
53
|
+
newconf = conf.dup
|
54
|
+
newconf[:name] = convert_rand_name(conf[:name])
|
55
|
+
haze = Hazetug::Haze[data[:compute_name]].new(newconf)
|
56
|
+
# Ensure a dynamic password loaded back from haze
|
57
|
+
if haze.config[:ssh_password]
|
58
|
+
newconf[:ssh_password] = haze.config[:ssh_password]
|
59
|
+
end
|
60
|
+
tug = Hazetug::Tug[data[:tug_name]].new(newconf, haze)
|
61
|
+
block.call(haze, tug)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def env_split
|
67
|
+
@env_split ||= begin
|
68
|
+
env = {}
|
69
|
+
arr = data[:opts][:env]
|
70
|
+
if arr
|
71
|
+
arr.each do |eq|
|
72
|
+
k, v = eq.split('=')
|
73
|
+
env[k] = v
|
74
|
+
end
|
75
|
+
end
|
76
|
+
env
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def convert_rand_name(name)
|
81
|
+
name.sub(/%rand\((\d+)\)%/) do |m|
|
82
|
+
rand(36**$1.to_i).to_s(36)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/hazetug/cli.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'gli'
|
2
|
+
require 'hazetug/cli/bootstrap'
|
3
|
+
|
4
|
+
class Hazetug
|
5
|
+
class CLI
|
6
|
+
include GLI::App
|
7
|
+
|
8
|
+
def run(args)
|
9
|
+
define_cli
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def define_cli
|
16
|
+
sort_help :manually
|
17
|
+
switch [:v, :verbose]
|
18
|
+
|
19
|
+
{
|
20
|
+
digital_ocean: 'DigitalOcean compute',
|
21
|
+
linode: 'Linode Compute'
|
22
|
+
}.each do |compute, compute_desc|
|
23
|
+
desc compute_desc
|
24
|
+
default_command :linode
|
25
|
+
|
26
|
+
command compute.to_s.gsub(/_/,'') do |compute_cmd|
|
27
|
+
|
28
|
+
compute_cmd.desc 'Provisions and bootstraps server'
|
29
|
+
compute_cmd.command :bootstrap do |op|
|
30
|
+
|
31
|
+
op.desc 'Bootstraps server using Knife'
|
32
|
+
op.arg_name 'task.yaml'
|
33
|
+
op.command :knife do |tug|
|
34
|
+
tug.arg_name nil
|
35
|
+
|
36
|
+
tug.flag [:v, :variables], :must_match => Array,
|
37
|
+
:desc => 'Set variable or comma-seperated list of variables (var1_1=hello)'
|
38
|
+
|
39
|
+
tug.flag [:n, :number], :default_value => 1,
|
40
|
+
:desc => 'Set number of created nodes, value from yaml is honored'
|
41
|
+
|
42
|
+
tug.flag [:c, :concurrency], :default_value => 1,
|
43
|
+
:desc => 'Set concurrency value, i.e. number of hosts bootstraped simultaneously'
|
44
|
+
|
45
|
+
tug.flag [:b, :bootstrap], :default_value => 'bootstrap.erb',
|
46
|
+
:desc => 'Set path to knife bootstrap.erb file'
|
47
|
+
|
48
|
+
tug.action do |gopts, opts, args|
|
49
|
+
|
50
|
+
if args.empty?
|
51
|
+
commands[:help].execute({},{},tug.name_for_help)
|
52
|
+
exit 0
|
53
|
+
end
|
54
|
+
|
55
|
+
act = CLI::Action[:bootstrap].new
|
56
|
+
act.pass(
|
57
|
+
tug_name: :knife,
|
58
|
+
compute_name: compute,
|
59
|
+
cli: tug,
|
60
|
+
gopts: gopts,
|
61
|
+
opts: opts,
|
62
|
+
args: args
|
63
|
+
).execute
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'fog/core'
|
2
|
+
require 'fog/xml'
|
3
|
+
require 'fog/json'
|
4
|
+
require 'fog/core/parser'
|
5
|
+
|
6
|
+
class Hazetug
|
7
|
+
class Compute
|
8
|
+
autoload :Linode, 'fog/linode'
|
9
|
+
autoload :DigitalOcean, 'fog/digitalocean'
|
10
|
+
|
11
|
+
module CloudMixin
|
12
|
+
CLOUD_MODELS = [
|
13
|
+
:locations,
|
14
|
+
:flavors,
|
15
|
+
:images
|
16
|
+
]
|
17
|
+
|
18
|
+
def cloud?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def cloud_models
|
23
|
+
Hazetug::Compute::CloudMixin::CLOUD_MODELS
|
24
|
+
end
|
25
|
+
|
26
|
+
CLOUD_MODELS.each do |method_name|
|
27
|
+
define_method(method_name) do
|
28
|
+
collection = self.class.const_get(:CollectionMap) || {}
|
29
|
+
map_method = collection[method_name] ? collection[method_name] : method_name
|
30
|
+
fog.send(map_method)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Base
|
36
|
+
def initialize(*args)
|
37
|
+
klass = self.class.name.split('::').last
|
38
|
+
@fog = Fog::Compute.const_get(klass).new(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method_name, *args, &block)
|
42
|
+
# cache all missing methods calls
|
43
|
+
self.class.module_eval <<-EOS, __FILE__, __LINE__
|
44
|
+
def #{method_name}(*args, &block)
|
45
|
+
fog.#{method_name}(*args, &block)
|
46
|
+
end
|
47
|
+
EOS
|
48
|
+
fog.send(method_name, *args, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def collection_map(mapping)
|
53
|
+
@collection_map = mapping
|
54
|
+
unless const_defined?(:CollectionMap)
|
55
|
+
module_eval "CollectionMap = @collection_map"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :fog
|
63
|
+
end
|
64
|
+
|
65
|
+
class Linode < Base
|
66
|
+
include CloudMixin
|
67
|
+
collection_map({
|
68
|
+
:locations => :data_centers
|
69
|
+
})
|
70
|
+
end
|
71
|
+
|
72
|
+
class DigitalOcean < Base
|
73
|
+
include CloudMixin
|
74
|
+
collection_map({
|
75
|
+
:locations => :regions
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'psych'
|
2
|
+
|
3
|
+
class Hazetug
|
4
|
+
class Config
|
5
|
+
PATH = File.expand_path("~/.hazetug")
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def load
|
9
|
+
Fog.credentials_path = PATH
|
10
|
+
@credentials ||= begin
|
11
|
+
if File.exist?(PATH)
|
12
|
+
Psych.load_file(PATH)[Fog.credential.to_s]
|
13
|
+
else
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
@credentials[key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
Hazetug::Config.load
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Hazetug
|
2
|
+
class Haze
|
3
|
+
module CloudServer
|
4
|
+
|
5
|
+
def provision_server
|
6
|
+
ui.info "[#{compute_name}] creating server #{config[:name]}"
|
7
|
+
@server = compute.servers.create(server_args)
|
8
|
+
server.wait_for { ready? }
|
9
|
+
ui.info "[#{compute_name}] server #{config[:name]} created, ip: #{server.ssh_ip_address}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait_for_ssh
|
13
|
+
ssh_options = {}
|
14
|
+
ssh_opts = Hazetug::Tug.ssh_options_from(config)
|
15
|
+
ssh_options[:password] = ssh_opts[:ssh_password]
|
16
|
+
ssh_options[:paranoid] = ssh_opts[:host_key_verify] || false
|
17
|
+
ssh_options[:keys] = ssh_opts[:ssh_keys] || Hazetug.ssh_keys(compute_name)
|
18
|
+
server.username = ssh_opts[:ssh_user]
|
19
|
+
server.ssh_port = ssh_opts[:ssh_port]
|
20
|
+
server.ssh_options = ssh_options
|
21
|
+
ui.info "[#{compute_name}] waiting for active ssh on #{server.ssh_ip_address}"
|
22
|
+
server.wait_for(30) { sshable? }
|
23
|
+
rescue Fog::Errors::TimeoutError
|
24
|
+
ui.error "[#{compute_name}] ssh failed to #{config[:name]}, ip: #{server.ssh_ip_address}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def lookup(model, *args)
|
28
|
+
collection_method = model.to_s + 's'
|
29
|
+
compare_method = "compare_#{model}?"
|
30
|
+
found = compute.send(collection_method).select do |o|
|
31
|
+
self.send(compare_method, o, *args)
|
32
|
+
end
|
33
|
+
if found.size > 1
|
34
|
+
ui.error "More than one #{model} found for #{config[model]}"
|
35
|
+
elsif found.empty?
|
36
|
+
ui.fatal "#{model} not found for #{config[model]}"
|
37
|
+
raise ArgumentError, "Wrong argument #{config[model]}"
|
38
|
+
end
|
39
|
+
found.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def memory_in_megabytes(string)
|
43
|
+
mult = 1
|
44
|
+
mult = 1024 if string.match(/gb$/i)
|
45
|
+
string.to_i * mult
|
46
|
+
end
|
47
|
+
|
48
|
+
def image_from_string(string)
|
49
|
+
string.downcase.sub(/\s*lts\s*/, '').
|
50
|
+
sub(RE_BITS, '').
|
51
|
+
sub(/\s+$/, '').
|
52
|
+
gsub(/ /, '-')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Bits from string, when can't be fetched default is 64
|
56
|
+
def bits_from_string(string)
|
57
|
+
m = string.match(Haze::RE_BITS)
|
58
|
+
m ? m.captures.compact.first.to_s.to_i : 64
|
59
|
+
end
|
60
|
+
|
61
|
+
def server_args
|
62
|
+
@server_args ||= create_server_args
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_server_args
|
66
|
+
raise NotImplementedError, "#create_server_arguments not implemented"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|