hazetug 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 723a5d37ca91b5fde0e80ec539e3d6a94c1acfdf
4
- data.tar.gz: 8a73f894d14f9c9a91387e0cde7fa1027d7b5a57
3
+ metadata.gz: ebdf9a9772962644f814c2215eb1a788e935a317
4
+ data.tar.gz: 5886036f41c10cc8a7efb87dbce4a3bc21c20d31
5
5
  SHA512:
6
- metadata.gz: 5c3e424bc320a7c7d0fd03fc3ddb882e32b0b265ff682b5960a726c5c079f303d9afd9c67cb810ffe2ce82a8ccdbace8df7de95259c2a6bc2ab896a8cc62ca14
7
- data.tar.gz: 7992188b72be3ea5a17224c443ba7ad71806b5736f37ab06c75d56af9c0b9f2749ee781f03c6a37be0287bbad179606d545d9ae86dfda64456c5fc503b7daf43
6
+ metadata.gz: fbf02d130984ad264c559f943be0b146e84a92ab5999d0390eafab9ff03de631cadf5cf367856c4d1fbb484bb0c547700ece2b5d8c2eeb81706002207acbd5fe
7
+ data.tar.gz: 5ba4e8ff097d31ec9d3e5685ce3a0bf9c6a8165e65bdb45ecef0f6dc1deb92dd46430e0af17e89bcf1e5bc8ed9e3dddb459570279d2e533f4eeab3fda1e825af
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  Cloud provisioner and bootstrapper for DigitalOcean and Linode.
4
4
  Hazetug uses [fog cloud library](http://fog.io) to be able to easily append other cloud computes and *tugs* (bootstraps) hosts using:
5
5
 
6
- * Knife bootstrap.
6
+ * Knife bootstrap for chef-client.
7
+ * Knife bootstrap for chef-solo.
7
8
 
8
9
  ## Options
9
10
 
@@ -64,8 +65,6 @@ Hazetug uses [fog cloud library](http://fog.io) to be able to easily append othe
64
65
 
65
66
  ## Knife tug
66
67
 
67
- ### Knife tug bootstrap options
68
-
69
68
  <table>
70
69
  <tr>
71
70
  <td><b>Option</b></td>
@@ -89,6 +88,27 @@ Hazetug uses [fog cloud library](http://fog.io) to be able to easily append othe
89
88
  </tr>
90
89
  </table>
91
90
 
91
+ ## Solo tug
92
+
93
+ <table>
94
+ <tr>
95
+ <td><b>Option</b></td>
96
+ <td><b>Description</b></td>
97
+ <td><b>Default value</b></td>
98
+ </tr>
99
+ <tr>
100
+ <td><b><i>attributes_json</i></b></td>
101
+ <td>Hash of attributes prepared for chef-solo run. (It's merged with the run_list).</td>
102
+ <td><i>{}</i><td>
103
+ </tr>
104
+ <tr>
105
+ <td><b><i>berksfile</i></b></td>
106
+ <td>Path to Berksfile.</td>
107
+ <td></td>
108
+ </tr>
109
+ </table>
110
+
111
+
92
112
  ## Installation
93
113
 
94
114
  Add this line to your application's Gemfile:
@@ -151,7 +171,7 @@ All variables are merged using this 3-level priority.
151
171
 
152
172
  ### Bootstrap using knife
153
173
 
154
- Help for linode compute is given bellow:
174
+ Help for the linode compute is given bellow:
155
175
 
156
176
  ```
157
177
  NAME
@@ -169,6 +189,40 @@ COMMAND OPTIONS
169
189
 
170
190
  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
191
 
192
+ #### Client and Solo modes
193
+
194
+ It's possible to use *chef-client* or *chef-solo* to bootstrap a node. The **solo** mode is almost identical with the **client** mode, but with the only difference that it uses **berkshelf** to assist during the bootstrap process. Cookbooks are packaged and uploaded to the remote node and can be used by chef
195
+
196
+ You can initiate bootstrap in the solo mode, like the following:
197
+ <pre>
198
+ hazetug digitalocean bootstrap solo -b bootstrap.erb solo-task.yaml
199
+ </pre>
200
+
201
+ Solo bootstrap also uses bootstrap.erb, but there's no need to start client or use any data like **validation_key** or **start_chef** helper.
202
+
203
+ When defining a task, there additional options **berksfile** and **attributes**
204
+
205
+ ```yaml
206
+ ...
207
+ ...
208
+ bootstrap:
209
+ - name: solo-box
210
+ location: london
211
+ flavor: 1gb
212
+ image: ubuntu-14.04-x64
213
+ run_list: ["role[api]"]
214
+ attributes: {
215
+ "mycookbook": {
216
+ "settings": {}
217
+ }
218
+ }
219
+ ```
220
+ ```
221
+
222
+ Merged *attributes* with the *run_list* form the input data for running **chef-solo** they are available inside bootstrap.erb as `hazetug[:attributes_json]`. Berksfile can be set specifically in the bootstrap task list or if it's not set local *Berksfile* is tried by default.
223
+
224
+ Another important option which is also available in the *bootstrap.erb* file is `hazetug[:cookbooks_path]` which is path to an archive of cookbooks packaged by Berkshelf.
225
+
172
226
  #### Examples
173
227
 
174
228
  * Provisioning and bootstrapping 5 nodes, each 3 of them will be processed simultaneously:
@@ -0,0 +1,82 @@
1
+ <% require 'json' -%>
2
+ bash -c '
3
+
4
+ mkdir -p /etc/chef
5
+
6
+ cat > /etc/chef/validation.pem <<'EOP'
7
+ <%= validation_key %>
8
+ EOP
9
+
10
+ chmod 0600 /etc/chef/validation.pem
11
+
12
+ cat > /etc/chef/client.rb <<'EOP'
13
+ <%= config_content %>
14
+ EOP
15
+
16
+ cat > /etc/chef/first-boot.json <<'EOP'
17
+ {
18
+ "run_list": <%= (["role[bootstrap]"] + (hazetug[:bootstrap_list] || [])).to_json %>,
19
+ "bootstrap": {
20
+ "set_run_list": <%= (hazetug[:run_list] || []).to_json %>,
21
+ "set_environment": "<%= hazetug[:chef_environment] %>"
22
+ }
23
+ }
24
+ EOP
25
+
26
+ # --------------- Update ubuntu and Install packages
27
+
28
+ export DEBIAN_FRONTEND=noninteractive
29
+ apt-get update && apt-get upgrade -yq
30
+ apt-get clean
31
+ apt-get autoremove -yq
32
+
33
+ apt-get -yq install curl build-essential
34
+
35
+
36
+ # --------------- Set hostname
37
+
38
+ HOSTNAME="<%= hazetug[:name] %>"
39
+ echo "${HOSTNAME}" > /etc/hostname
40
+ ip=`/sbin/ifconfig eth0 | grep "\<inet\>" | cut -d ":" -f2 | cut -d " " -f1`
41
+ echo -e "\n# our own entry\n${ip} ${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts
42
+ cp /etc/hosts /tmp/hosts.orig
43
+ cat /tmp/hosts.orig | sed "s/127.0.0.1.*/127.0.0.1 localhost localhost.localdomain/" > /etc/hosts
44
+ rm /tmp/hosts.orig
45
+ hostname -F /etc/hostname
46
+
47
+ # --------------- Set private network on Linode
48
+
49
+ if [ "<%= hazetug[:compute_name] %>" = "linode" ]; then
50
+
51
+ cat >> /etc/network/interfaces <<'EOP'
52
+
53
+ auto eth0:0
54
+ iface eth0:0 inet static
55
+ address <%= hazetug[:private_ip_address] %>
56
+ netmask 255.255.128.0
57
+ EOP
58
+ ifup eth0:0
59
+
60
+ fi
61
+
62
+
63
+ # --------------- Install Chef
64
+
65
+ curl -L https://get.rvm.io | bash -s <%= hazetug[:rvm_version] || 'stable' %>
66
+ source /etc/profile.d/rvm.sh
67
+
68
+ rvm install <%= hazetug[:ruby_version] || "ruby-2.1.2" %> --autolibs=3
69
+ rvm use <%= hazetug[:ruby_version] || "ruby-2.1.2" %> --default
70
+
71
+ cat >/tmp/chef_gemfile<<EOF
72
+ source "http://rubygems.org"
73
+ gem "chef", "<%= hazetug[:chef_version] || "11.14.6" %>"
74
+ EOF
75
+
76
+ bundle install --gemfile /tmp/chef_gemfile
77
+ rm /tmp/chef_gemfile
78
+
79
+ # --------------- Start Chef (bootstrap run)
80
+
81
+ echo "<%= start_chef %>"
82
+ <%= start_chef %>'
@@ -0,0 +1,64 @@
1
+ <% require 'json' -%>
2
+ bash -c '
3
+
4
+ mkdir -p /etc/chef
5
+
6
+ cat > /etc/chef/first-boot.json <<'EOP'
7
+ <%= hazetug[:attributes_json] %>
8
+ EOP
9
+
10
+ # --------------- Update ubuntu and Install packages
11
+
12
+ export DEBIAN_FRONTEND=noninteractive
13
+ apt-get update && apt-get upgrade -yq
14
+ apt-get clean
15
+ apt-get autoremove -yq
16
+
17
+ apt-get -yq install curl build-essential
18
+
19
+
20
+ # --------------- Set hostname
21
+
22
+ HOSTNAME="<%= hazetug[:name] %>"
23
+ echo "${HOSTNAME}" > /etc/hostname
24
+ ip=`/sbin/ifconfig eth0 | grep "\<inet\>" | cut -d ":" -f2 | cut -d " " -f1`
25
+ echo -e "\n# our own entry\n${ip} ${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts
26
+ cp /etc/hosts /tmp/hosts.orig
27
+ cat /tmp/hosts.orig | sed "s/127.0.0.1.*/127.0.0.1 localhost localhost.localdomain/" > /etc/hosts
28
+ rm /tmp/hosts.orig
29
+ hostname -F /etc/hostname
30
+
31
+ # --------------- Set private network on Linode
32
+
33
+ if [ "<%= hazetug[:compute_name] %>" = "linode" ]; then
34
+
35
+ cat >> /etc/network/interfaces <<'EOP'
36
+
37
+ auto eth0:0
38
+ iface eth0:0 inet static
39
+ address <%= hazetug[:private_ip_address] %>
40
+ netmask 255.255.128.0
41
+ EOP
42
+ ifup eth0:0
43
+
44
+ fi
45
+
46
+ # --------------- Install Chef
47
+
48
+ curl -L https://get.rvm.io | bash -s <%= hazetug[:rvm_version] || 'stable' %>
49
+ source /etc/profile.d/rvm.sh
50
+
51
+ rvm install <%= hazetug[:ruby_version] || "ruby-2.1.2" %> --autolibs=3
52
+ rvm use <%= hazetug[:ruby_version] || "ruby-2.1.2" %> --default
53
+
54
+ cat >/tmp/chef_gemfile<<EOF
55
+ source "http://rubygems.org"
56
+ gem "chef", "<%= hazetug[:chef_version] || "11.14.6" %>"
57
+ EOF
58
+
59
+ bundle install --gemfile /tmp/chef_gemfile
60
+ rm /tmp/chef_gemfile /tmp/chef_gemfile.lock
61
+
62
+ # --------------- Start Chef in solo mode
63
+
64
+ chef-solo -j /etc/chef/first-boot.json -r <%= hazetug[:cookbooks_file] %>'
data/hazetug.gemspec CHANGED
@@ -7,9 +7,9 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "hazetug"
8
8
  spec.version = Hazetug::VERSION
9
9
  spec.authors = ["Denis Barishev"]
10
- spec.email = ["denz@twiket.com"]
10
+ spec.email = ["dennybaa@gmail.com"]
11
11
  spec.summary = %q{Cloud provisoner tool}
12
- spec.description = %q{Hazetug uses fog cloud library and he}
12
+ spec.description = %q{Provisions and bootstraps nodes using knife}
13
13
  spec.homepage = "https://github.com/dennybaa/hazetug"
14
14
  spec.license = "MIT"
15
15
 
@@ -19,10 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
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"
22
+ spec.add_development_dependency "rake", "~> 0"
23
+ spec.add_dependency "psych", "~> 0"
24
+ spec.add_dependency "fog", "~> 0"
25
+ spec.add_dependency "chef", "~> 11.0"
26
+ spec.add_dependency "gli", "~> 2.0"
27
+ spec.add_dependency "agent", "~> 0"
28
+ spec.add_dependency "berkshelf", "~> 3.0"
28
29
  end
@@ -3,7 +3,6 @@ require 'hazetug/cli/action'
3
3
  require 'hazetug/tug'
4
4
  require 'hazetug/task'
5
5
 
6
-
7
6
  class Hazetug
8
7
  class CLI
9
8
  class Bootstrap < Action
@@ -26,11 +25,15 @@ class Hazetug
26
25
 
27
26
  def provision_and_bootstrap(haze, tug, channel, waitgroup)
28
27
  haze.provision
29
- tug.bootstrap({
30
- args: data[:args],
31
- opts: data[:opts],
32
- gopts: data[:gopts]
33
- })
28
+ if haze.ready?
29
+ tug.load_haze_config(haze.config_for_tug)
30
+
31
+ tug.bootstrap({
32
+ args: data[:args],
33
+ opts: data[:opts],
34
+ gopts: data[:gopts]
35
+ })
36
+ end
34
37
  rescue
35
38
  # Exeception will be lost, since we run inside goproc,
36
39
  # ie. as soon as waitgroup is empty all process exit.
@@ -62,7 +65,7 @@ class Hazetug
62
65
  newconf[:ssh_password] = haze.config[:ssh_password]
63
66
  end
64
67
 
65
- tug = Hazetug::Tug[data[:tug_name]].new(newconf, haze)
68
+ tug = Hazetug::Tug[data[:tug_name]].new(newconf)
66
69
  block.call(haze, tug)
67
70
  end
68
71
  end
data/lib/hazetug/cli.rb CHANGED
@@ -29,40 +29,45 @@ class Hazetug
29
29
  compute_cmd.desc 'Provisions and bootstraps server'
30
30
  compute_cmd.command :bootstrap do |op|
31
31
 
32
- op.desc 'Bootstraps server using Knife'
33
- op.arg_name 'task.yaml'
34
- op.command :knife do |tug|
35
- tug.arg_name nil
36
-
37
- tug.flag [:v, :variables], :must_match => Array,
38
- :desc => 'Set variable or comma-seperated list of variables (var1_1=hello)'
39
-
40
- tug.flag [:n, :number], :default_value => 1,
41
- :desc => 'Set number of created nodes, value from yaml is honored'
42
-
43
- tug.flag [:c, :concurrency], :default_value => 1,
44
- :desc => 'Set concurrency value, i.e. number of hosts bootstraped simultaneously'
45
-
46
- tug.flag [:b, :bootstrap], :default_value => 'bootstrap.erb',
47
- :desc => 'Set path to knife bootstrap.erb file'
48
-
49
- tug.action do |gopts, opts, args|
50
-
51
- if args.empty?
52
- commands[:help].execute({},{},tug.name_for_help)
53
- exit 0
32
+ op.flag [:v, :variables], :must_match => Array,
33
+ :desc => 'Set variable or comma-seperated list of variables (var1_1=hello)'
34
+
35
+ op.flag [:n, :number], :default_value => 1,
36
+ :desc => 'Set number of created nodes, value from yaml is honored'
37
+
38
+ op.flag [:c, :concurrency], :default_value => 1,
39
+ :desc => 'Set concurrency value, i.e. number of hosts bootstraped simultaneously'
40
+
41
+ op.flag [:b, :bootstrap], :default_value => 'bootstrap.erb',
42
+ :desc => "Set path to knife bootstrap.erb file"
43
+
44
+ [:knife, :solo].each do |tug_cmd|
45
+ mode = "#{tug_cmd == :knife ? 'client' : 'solo'}"
46
+
47
+
48
+ op.desc "Bootstraps server using Knife in #{mode} mode"
49
+ op.arg_name '<task.yaml>'
50
+ op.command tug_cmd do |tug|
51
+
52
+ tug.action do |gopts, opts, args|
53
+
54
+ if args.empty?
55
+ commands[:help].execute({},{},tug.name_for_help)
56
+ exit 0
57
+ end
58
+
59
+ act = CLI::Action[:bootstrap].new
60
+ act.pass(
61
+ tug_name: tug_cmd,
62
+ compute_name: compute,
63
+ cli: tug,
64
+ gopts: gopts,
65
+ opts: opts,
66
+ args: args
67
+ ).execute
54
68
  end
55
-
56
- act = CLI::Action[:bootstrap].new
57
- act.pass(
58
- tug_name: :knife,
59
- compute_name: compute,
60
- cli: tug,
61
- gopts: gopts,
62
- opts: opts,
63
- args: args
64
- ).execute
65
69
  end
70
+
66
71
  end
67
72
 
68
73
  end
@@ -72,4 +77,4 @@ class Hazetug
72
77
  end
73
78
 
74
79
  end
75
- end
80
+ end
@@ -15,11 +15,12 @@ class Hazetug
15
15
  ssh_options[:password] = ssh_opts[:ssh_password]
16
16
  ssh_options[:paranoid] = ssh_opts[:host_key_verify] || false
17
17
  ssh_options[:keys] = ssh_opts[:ssh_keys] || Hazetug.ssh_keys(compute_name)
18
- server.username = ssh_opts[:ssh_user]
18
+ server.username = ssh_opts[:ssh_user] || 'root'
19
19
  server.ssh_port = ssh_opts[:ssh_port]
20
20
  server.ssh_options = ssh_options
21
21
  ui.info "[#{compute_name}] waiting for active ssh on #{server.ssh_ip_address}"
22
22
  server.wait_for(30) { sshable? }
23
+ @ready = true
23
24
  rescue Fog::Errors::TimeoutError
24
25
  ui.error "[#{compute_name}] ssh failed to #{config[:name]}, ip: #{server.ssh_ip_address}"
25
26
  end
@@ -20,7 +20,7 @@ class Hazetug
20
20
  latest = /^Latest #{config[:bits]} bit/
21
21
  {
22
22
  # linode node name can't contain dots
23
- :name => config[:name].gsub(/./,'-'),
23
+ :name => config[:name].gsub(/\./,'-'),
24
24
  :data_center => lookup(:location),
25
25
  :flavor => lookup(:flavor),
26
26
  :image => lookup(:image),
data/lib/hazetug/haze.rb CHANGED
@@ -16,7 +16,7 @@ class Hazetug
16
16
  @compute = Hazetug::Compute.const_get(compute_name).new
17
17
  @config = configure(config)
18
18
  @server = nil
19
- @sshable = false
19
+ @ready = false
20
20
  end
21
21
 
22
22
  def provision
@@ -28,6 +28,10 @@ class Hazetug
28
28
  exit(1)
29
29
  end
30
30
 
31
+ def ready?
32
+ @ready
33
+ end
34
+
31
35
  def configure(config)
32
36
  input = config.keys.map(&:to_sym)
33
37
  requires = self.class.requires
@@ -67,6 +71,14 @@ class Hazetug
67
71
  def private_ip_address
68
72
  end
69
73
 
74
+ def config_for_tug
75
+ {
76
+ compute_name: compute_name.downcase,
77
+ public_ip_address: (public_ip_address || server.ssh_ip_address rescue nil),
78
+ private_ip_address: private_ip_address
79
+ }
80
+ end
81
+
70
82
  protected
71
83
 
72
84
  def provision_server
@@ -1,104 +1,26 @@
1
1
  require 'chef/knife/ssh'
2
2
  require 'chef/knife/bootstrap'
3
- require 'hazetug/tug'
4
-
5
- # Monkey Patch:)
6
- # Extend knife bootstrap context with our data
7
- class Chef::Knife::Core::BootstrapContext
8
- def hazetug; @config[:hazetug]; end
9
- end
10
-
11
- class Chef::Knife::Ssh < Chef::Knife
12
- def run
13
- extend Chef::Mixin::Command
14
- @longest = 0
15
- configure_attribute
16
- configure_user
17
- configure_password
18
- configure_identity_file
19
- configure_gateway
20
- configure_session
21
- exit_status = ssh_command(@name_args[1..-1].join(" "))
22
- session.close
23
-
24
- exit_status
25
- end
26
- end
3
+ require 'hazetug/tug/knife_base'
27
4
 
28
5
  class Hazetug
29
6
  class Tug
30
- class Knife < Tug
7
+ class Knife < KnifeBase
31
8
 
32
- def bootstrap_server
33
- [
34
- :template_file,
35
- :identity_file,
36
- :ssh_user,
37
- :ssh_password,
38
- :host_key_verify
39
- ].each do |opt|
40
- kb.config[opt] = bootstrap_options[opt]
41
- end
42
- [
9
+ def initialize(config={}, haze=nil)
10
+ super
11
+ @chef_option_list = [
43
12
  :environment,
44
13
  :chef_server_url,
45
14
  :validation_key
46
- ].each do |opt|
47
- Chef::Config[opt] = bootstrap_options[opt]
48
- end
49
- kb.name_args = [haze.server.ssh_ip_address]
50
- kb.run
51
- rescue Hazetug::Exception => e
52
- ui.error(e.message); 1
53
- ensure
54
- @kb and @kb.ui.stdout.close
15
+ ]
55
16
  end
56
17
 
57
- def kb
58
- @kb ||= begin
59
- lf = create_log_file
60
- Chef::Knife::Bootstrap.load_deps
61
- kb = Chef::Knife::Bootstrap.new
62
- kb.config[:hazetug] = config
63
- kb.ui = Chef::Knife::UI.new(lf, lf, lf, {verbosity: 2})
64
- kb
65
- end
66
- end
67
-
68
- def bootstrap_options
69
- @bootstrap_options ||= begin
70
- template = options[:opts][:bootstrap] || 'bootstrap.erb'
71
- validation = config[:chef_validation_key] || 'validation.pem'
72
-
73
- files = [template, validation].map {|f| File.expand_path(f)}
74
- notfound = files.select {|f| !File.exist?(f)}
75
- notfound.empty? or
76
- raise Hazetug::Exception, "File(s) not found: #{notfound.join(', ')}"
77
-
78
- opts = {}
79
- opts[:validation_key] = File.expand_path(validation)
80
- opts[:template_file] = File.expand_path(template)
81
- opts[:ssh_user] = config[:ssh_user] || 'root'
82
- opts[:ssh_password] = config[:ssh_password]
83
- opts[:environment] = config[:chef_environment]
84
- opts[:host_key_verify] = config[:host_key_verify] || false
85
- opts[:chef_server_url] = config[:chef_server_url]
86
- opts[:identity_file] = preferred_ssh_identity if not opts[:ssh_password]
87
- opts
88
- end
89
- end
90
-
91
- def preferred_ssh_identity
92
- @preferred_ssh_identity ||= begin
93
- compute = Hazetug.leaf_klass_name(haze.class.name).downcase
94
- identity = config[:identity_file]
95
- key_path = (Hazetug::Config["#{compute}_ssh_keys"] || []).first
96
- if identity.nil? && key_path.nil?
97
- raise Hazetug::Exception, "identity file not specified, use #{compute}_ssh_keys or identity_file"
98
- end
99
- identity ||= File.expand_path(key_path)
100
- identity
18
+ def bootstrap_config
19
+ super
20
+ @chef_option_list.each do |opt|
21
+ Chef::Config[opt] = bootstrap_options[opt]
101
22
  end
23
+ check_bootstrap_files! :validation_key, :template_file
102
24
  end
103
25
 
104
26
  end
@@ -0,0 +1,164 @@
1
+ require 'erubis'
2
+ require 'chef/knife/ssh'
3
+ require 'chef/knife/bootstrap'
4
+ require 'chef/knife/core/bootstrap_context'
5
+ require 'hazetug/tug'
6
+
7
+ # Extend knife bootstrap context with our data.
8
+ # It's monkey patching, yey :)
9
+ class Chef::Knife::Bootstrap
10
+ def render_template(template=nil)
11
+ context = Chef::Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config)
12
+ @extended_bootstrap_variables.each do |s, v|
13
+ context.instance_variable_set(s, v)
14
+ end
15
+ Erubis::Eruby.new(template).evaluate(context)
16
+ end
17
+
18
+ def add_variable_to_bootstrap_context(variable_sym, value)
19
+ @extended_bootstrap_variables ||= {}
20
+ @extended_bootstrap_variables[variable_sym] = value if value
21
+ end
22
+ end
23
+
24
+ class Chef::Knife::Core::BootstrapContext
25
+ def hazetug; @hazetug; end
26
+ end
27
+
28
+ class Chef::Knife::Ssh < Chef::Knife
29
+ def run
30
+ extend Chef::Mixin::Command
31
+ @longest = 0
32
+ configure_attribute
33
+ configure_user
34
+ configure_password
35
+ configure_identity_file
36
+ configure_gateway
37
+ configure_session
38
+ exit_status = ssh_command(@name_args[1..-1].join(" "))
39
+ session.close
40
+
41
+ exit_status
42
+ end
43
+ end
44
+
45
+ class Hazetug
46
+ class Tug
47
+ class KnifeBase < Tug
48
+
49
+ def initialize(config={})
50
+ super
51
+ @bootstrap_option_list = [
52
+ :template_file,
53
+ :identity_file,
54
+ :ssh_user,
55
+ :ssh_password,
56
+ :host_key_verify
57
+ ]
58
+ end
59
+
60
+ # Bootstraps the remote server using knife bootstrap.
61
+ def bootstrap_server
62
+ bootstrap_config
63
+
64
+ bootstrap_init
65
+ bootstrap_run
66
+ bootstrap_cleanup
67
+ rescue Hazetug::Exception => e
68
+ ui.error(e.message); 1
69
+ ensure
70
+ knife and knife.ui.stdout.close
71
+ end
72
+
73
+ # Configures knife bootstrap by setting the necessary options.
74
+ def bootstrap_config
75
+ knife.add_variable_to_bootstrap_context(:@hazetug, config)
76
+
77
+ @bootstrap_option_list.each do |opt|
78
+ knife.config[opt] = bootstrap_options[opt]
79
+ end
80
+
81
+ # Check ssh identity
82
+ cred = [
83
+ hazetug_identity, bootstrap_options[:identity_file],
84
+ bootstrap_options[:ssh_password]
85
+ ]
86
+
87
+ if cred.all?(&:nil?)
88
+ msg = "No identity (inc. ssh_password) found. Check ssh_password," <<
89
+ " identity_file options or #{config[:compute_name]}_ssh_keys " <<
90
+ "hazetug parameter."
91
+
92
+ raise Hazetug::Exception, msg
93
+ end
94
+ end
95
+
96
+ # Pre knife bootstrap hook.
97
+ def bootstrap_init
98
+ end
99
+
100
+ # After knife bootstrap hook.
101
+ def bootstrap_cleanup
102
+ end
103
+
104
+ # Initiates knife bootstrap run.
105
+ def bootstrap_run
106
+ knife.name_args = [config[:public_ip_address]]
107
+ knife.run
108
+ end
109
+
110
+ # Initializes knife bootstrap, default output is redirected into file.
111
+ def knife
112
+ @knife ||= begin
113
+ lf = create_log_file
114
+ Chef::Knife::Bootstrap.load_deps
115
+ kb = Chef::Knife::Bootstrap.new
116
+ kb.ui = Chef::Knife::UI.new(lf, lf, lf, {verbosity: 2})
117
+ kb
118
+ end
119
+ end
120
+
121
+ # Extracts bootstrap options from the hazetug configuration.
122
+ def bootstrap_options
123
+ @bootstrap_options ||= begin
124
+ opts = {}
125
+
126
+ opts[:ssh_user] = config[:ssh_user] || 'root'
127
+ opts[:ssh_password] = config[:ssh_password]
128
+ if opts[:ssh_password].nil?
129
+ opts[:identity_file] = config[:identity_file] || hazetug_identity
130
+ end
131
+
132
+ template = options[:opts][:bootstrap] || 'bootstrap.erb'
133
+ validation = config[:chef_validation_key] || 'validation.pem'
134
+
135
+ opts[:validation_key] = File.expand_path(validation)
136
+ opts[:template_file] = File.expand_path(template)
137
+ opts[:environment] = config[:chef_environment]
138
+ opts[:host_key_verify] = config[:host_key_verify] || false
139
+ opts[:chef_server_url] = config[:chef_server_url]
140
+ opts
141
+ end
142
+ end
143
+
144
+ # Lookup ssh identity key(s) in the hazetug configuration.
145
+ def hazetug_identity
146
+ @hazetug_identity ||= begin
147
+ compute = config[:compute_name]
148
+ key_path = (Hazetug::Config["#{compute}_ssh_keys"] || []).first
149
+ key_path and File.expand_path(key_path)
150
+ end
151
+ end
152
+
153
+ # Check if files exist otherwise fail.
154
+ def check_bootstrap_files!(*list_of_opts)
155
+ list = list_of_opts.map {|k| bootstrap_options[k]}
156
+ files = list.map {|f| File.expand_path(f.to_s)}
157
+ notfound = files.select {|f| !File.exist?(f)}
158
+ notfound.empty? or
159
+ raise Hazetug::Exception, "File(s) not found: #{notfound.join(', ')}"
160
+ end
161
+
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,90 @@
1
+ require 'chef/knife/ssh'
2
+ require 'chef/knife/bootstrap'
3
+ require 'berkshelf'
4
+ require 'berkshelf/berksfile'
5
+ require 'json'
6
+ require 'hazetug/tug/knife_base'
7
+
8
+ class Hazetug
9
+ class Tug
10
+ class Solo < KnifeBase
11
+
12
+ def initialize(config={})
13
+ super
14
+ @upload_dest = '/tmp/hazetug-cookbooks.tar.gz'
15
+ end
16
+
17
+ def bootstrap_config
18
+ super
19
+ config[:attributes_json] = json_attributes
20
+ config[:cookbooks_file] = @upload_dest
21
+
22
+ check_bootstrap_files! :template_file, :berksfile
23
+ end
24
+
25
+ def bootstrap_init
26
+ upload_berks_package
27
+ end
28
+
29
+ def bootstrap_cleanup
30
+ ssh = Fog::SSH.new(config[:public_ip_address], ssh_username, ssh_options)
31
+ ssh.run("test -f #{@upload_dest} && rm #{@upload_dest}")
32
+ end
33
+
34
+ def berks_package(&block)
35
+ berks = Berkshelf::Berksfile.from_file(bootstrap_options[:berksfile])
36
+
37
+ Dir::Tmpname.create('hazetug-cookbooks') do |path|
38
+ begin
39
+ berks.package(path)
40
+ block.call(path)
41
+ ensure
42
+ File.unlink(path) if File.exist?(path)
43
+ end
44
+ end
45
+ end
46
+
47
+ def upload_berks_package
48
+ berks_package do |path|
49
+ scp = Fog::SCP.new(config[:public_ip_address], ssh_username, ssh_options)
50
+ scp.upload(path, @upload_dest)
51
+ end
52
+ end
53
+
54
+ def bootstrap_options
55
+ @bootstrap_options ||= begin
56
+ opts = super
57
+ opts[:berksfile] = config[:berksfile] || 'Berksfile'
58
+ opts
59
+ end
60
+ end
61
+
62
+ def json_attributes
63
+ @json_attributes ||= begin
64
+ hash = config[:attributes] || {}
65
+ hash[:run_list] = config[:run_list] || []
66
+ JSON.pretty_generate(hash)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def ssh_options
73
+ @ssh_options ||= begin
74
+ ssh_options = {}
75
+ ssh_opts = Hazetug::Tug.ssh_options_from(config)
76
+ ssh_options[:password] = ssh_opts[:ssh_password]
77
+ ssh_options[:paranoid] = ssh_opts[:host_key_verify] || false
78
+ ssh_options[:keys] = ssh_opts[:ssh_keys] || Hazetug.ssh_keys(config[:compute_name])
79
+ ssh_options[:port] = ssh_opts[:ssh_port]
80
+ ssh_options
81
+ end
82
+ end
83
+
84
+ def ssh_username
85
+ config[:ssh_user] || 'root'
86
+ end
87
+
88
+ end
89
+ end
90
+ end
data/lib/hazetug/tug.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'hazetug/tug/knife'
2
+ require 'hazetug/tug/solo'
2
3
  require 'hazetug/ui'
3
4
  require 'chef/mash'
4
5
 
@@ -12,10 +13,9 @@ class Hazetug
12
13
  ]
13
14
  LOGDIR = "#{Dir.pwd}/logs"
14
15
 
15
- attr_reader :haze, :config, :options
16
+ attr_reader :config, :options
16
17
 
17
- def initialize(config={}, haze=nil)
18
- @haze = haze
18
+ def initialize(config={})
19
19
  @config = config
20
20
  end
21
21
 
@@ -24,22 +24,23 @@ class Hazetug
24
24
  end
25
25
 
26
26
  def bootstrap(options={})
27
- if haze && haze.server && haze.server.sshable?
28
- @options = options
29
- haztug_set_variables
30
- ip = config[:public_ip_address]
31
- ui.msg "[#{tug_name}] bootstraping server #{haze.config[:name]}, ip: #{ip}"
32
- exit_status = bootstrap_server
33
- if exit_status.is_a?(Fixnum) && exit_status != 0
34
- ui.error "[#{tug_name}] bootstraping server #{haze.config[:name]} failed."
35
- else
36
- ui.msg "[#{tug_name}] bootstraping server #{haze.config[:name]} done."
37
- end
27
+ @options = options
28
+ ip = config[:public_ip_address]
29
+
30
+ ui.msg "[#{tug_name}] bootstraping server #{config[:name]}, ip: #{ip}"
31
+ exit_status = bootstrap_server
32
+
33
+ if exit_status.is_a?(Fixnum) && exit_status != 0
34
+ ui.error "[#{tug_name}] bootstraping server #{config[:name]} failed."
38
35
  else
39
- ui.error "#{haze.compute_name} skipping bootstrap, server #{haze.config[:name]} not ready"
36
+ ui.msg "[#{tug_name}] bootstraping server #{config[:name]} done."
40
37
  end
41
38
  rescue Hazetug::Exception => e
42
- ui.error "[#{haze.compute_name}] #{e.message}"
39
+ ui.error "[#{config[:compute_name]}] #{e.message}"
40
+ end
41
+
42
+ def load_haze_config(hash)
43
+ @config.merge!(hash)
43
44
  end
44
45
 
45
46
  class << self
@@ -58,25 +59,13 @@ class Hazetug
58
59
  end
59
60
  end
60
61
 
61
- private
62
-
63
- def haztug_set_variables
64
- {
65
- compute_name: haze.compute_name.downcase,
66
- public_ip_address: (haze.public_ip_address || haze.server.ssh_ip_address rescue nil),
67
- private_ip_address: haze.private_ip_address
68
- }.each do |key, value|
69
- config[key] = value if value
70
- end
71
- end
72
-
73
62
  protected
74
63
 
75
64
  def create_log_file
76
65
  unless File.directory?(LOGDIR)
77
66
  Dir.mkdir(LOGDIR)
78
67
  end
79
- log = File.new("#{LOGDIR}/#{haze.config[:name]}", "w+")
68
+ log = File.new("#{LOGDIR}/#{config[:name]}", "w+")
80
69
  log.sync = true
81
70
  log
82
71
  end
@@ -1,3 +1,3 @@
1
1
  class Hazetug
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hazetug
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Barishev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-28 00:00:00.000000000 Z
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,89 +28,103 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: psych
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: fog
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: chef
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 11.10.0
75
+ version: '11.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 11.10.0
82
+ version: '11.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: gli
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '2.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '2.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: agent
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- description: Hazetug uses fog cloud library and he
111
+ - !ruby/object:Gem::Dependency
112
+ name: berkshelf
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ description: Provisions and bootstraps nodes using knife
112
126
  email:
113
- - denz@twiket.com
127
+ - dennybaa@gmail.com
114
128
  executables:
115
129
  - hazetug
116
130
  extensions: []
@@ -122,6 +136,8 @@ files:
122
136
  - README.md
123
137
  - Rakefile
124
138
  - bin/hazetug
139
+ - examples/bootstrap-client.erb
140
+ - examples/bootstrap-solo.erb
125
141
  - hazetug.gemspec
126
142
  - lib/hazetug.rb
127
143
  - lib/hazetug/cli.rb
@@ -137,6 +153,8 @@ files:
137
153
  - lib/hazetug/task.rb
138
154
  - lib/hazetug/tug.rb
139
155
  - lib/hazetug/tug/knife.rb
156
+ - lib/hazetug/tug/knife_base.rb
157
+ - lib/hazetug/tug/solo.rb
140
158
  - lib/hazetug/ui.rb
141
159
  - lib/hazetug/version.rb
142
160
  homepage: https://github.com/dennybaa/hazetug