chef-metal-ssh 0.0.4 → 0.1.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.
- data/README.md +58 -8
- data/lib/chef/provider/ssh_cluster.rb +31 -0
- data/lib/chef/provider/ssh_target.rb +78 -0
- data/lib/chef/resource/ssh_cluster.rb +21 -0
- data/lib/chef/resource/ssh_target.rb +71 -0
- data/lib/chef_metal/driver_init/ssh.rb +3 -0
- data/lib/chef_metal_ssh.rb +16 -1
- data/lib/chef_metal_ssh/machine_registry.rb +230 -0
- data/lib/chef_metal_ssh/ssh_driver.rb +388 -0
- data/lib/chef_metal_ssh/version.rb +1 -1
- metadata +20 -15
- data/lib/chef_metal/provisioner_init/ssh_init.rb +0 -4
- data/lib/chef_metal_ssh/ssh_provisioner.rb +0 -300
data/README.md
CHANGED
@@ -20,20 +20,70 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
+
* valid machine options: one of the two is required, ip address is boss if both given
|
24
|
+
|
25
|
+
:ip_address,
|
26
|
+
:fqdn - this can be a shortname too as long as it resolves
|
27
|
+
|
28
|
+
|
29
|
+
* valid ssh options
|
30
|
+
|
31
|
+
:auth_methods,
|
32
|
+
:bind_address,
|
33
|
+
:compression,
|
34
|
+
:compression_level,
|
35
|
+
:config,
|
36
|
+
:encryption,
|
37
|
+
:forward_agent,
|
38
|
+
:hmac,
|
39
|
+
:host_key,
|
40
|
+
:keepalive,
|
41
|
+
:keepalive_interval,
|
42
|
+
:kex,
|
43
|
+
:keys,
|
44
|
+
:key_data,
|
45
|
+
:languages,
|
46
|
+
:logger,
|
47
|
+
:paranoid,
|
48
|
+
:password,
|
49
|
+
:port,
|
50
|
+
:proxy,
|
51
|
+
:rekey_blocks_limit,
|
52
|
+
:rekey_limit,
|
53
|
+
:rekey_packet_limit,
|
54
|
+
:timeout,
|
55
|
+
:verbose,
|
56
|
+
:global_known_hosts_file,
|
57
|
+
:user_known_hosts_file,
|
58
|
+
:host_key_alias,
|
59
|
+
:host_name,
|
60
|
+
:user,
|
61
|
+
:properties,
|
62
|
+
:passphrase,
|
63
|
+
:keys_only,
|
64
|
+
:max_pkt_size,
|
65
|
+
:max_win_size, :send_env,
|
66
|
+
:use_agent
|
67
|
+
|
68
|
+
* machine resource example:
|
69
|
+
|
70
|
+
require 'chef_metal_ssh'
|
71
|
+
|
72
|
+
with_ssh_cluster("~/metal_ssh")
|
73
|
+
|
23
74
|
machine "one" do
|
24
|
-
action :
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
'password' => 'vagrant'
|
31
|
-
}
|
75
|
+
action [:ready, :converge]
|
76
|
+
machine_options 'ip_address' => '192.168.33.21',
|
77
|
+
'ssh_options' => {
|
78
|
+
'user' => 'vagrant',
|
79
|
+
'password' => 'vagrant'
|
80
|
+
}
|
32
81
|
recipe 'ssh_test::remote1'
|
33
82
|
notifies :create, 'machine[two]'
|
34
83
|
notifies :run, 'execute[run_touch1]'
|
35
84
|
end
|
36
85
|
|
86
|
+
|
37
87
|
To test it out, clone the repo:
|
38
88
|
|
39
89
|
`git clone https://github.com/double-z/chef-metal-ssh.git`
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'chef/provider/lwrp_base'
|
2
|
+
require 'cheffish'
|
3
|
+
|
4
|
+
class Chef::Provider::SshCluster < Chef::Provider::LWRPBase
|
5
|
+
|
6
|
+
use_inline_resources
|
7
|
+
|
8
|
+
def whyrun_supported?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
action :create do
|
13
|
+
the_base_path = new_resource.path
|
14
|
+
Cheffish.inline_resource(self, :create) do
|
15
|
+
directory the_base_path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
action :delete do
|
20
|
+
the_base_path = new_resource.path
|
21
|
+
Cheffish.inline_resource(self, :delete) do
|
22
|
+
directory the_base_path do
|
23
|
+
action :delete
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_current_resource
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# require 'json'
|
2
|
+
# require 'chef/provider/lwrp_base'
|
3
|
+
# require 'chef_metal/provider_action_handler'
|
4
|
+
|
5
|
+
# class Chef::Provider::SshTarget < Chef::Provider::LWRPBase
|
6
|
+
|
7
|
+
# include ChefMetal::ProviderActionHandler
|
8
|
+
|
9
|
+
# use_inline_resources
|
10
|
+
|
11
|
+
# def whyrun_supported?
|
12
|
+
# true
|
13
|
+
# end
|
14
|
+
|
15
|
+
# action :register do
|
16
|
+
|
17
|
+
# ip_address = new_resource.name
|
18
|
+
# target_registration_file_json = target_registration_file_to_json(new_resource)
|
19
|
+
# base_ssh_cluster_path = new_resource.ssh_cluster_path
|
20
|
+
# puts
|
21
|
+
# puts '::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json")'
|
22
|
+
# puts ::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json")
|
23
|
+
|
24
|
+
# unless ::File.exists?(::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json"))
|
25
|
+
# ChefMetal.inline_resource(self) do
|
26
|
+
# file ::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json") do
|
27
|
+
# Chef::Log.info(::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json"))
|
28
|
+
# content target_registration_file_json
|
29
|
+
# not_if { ::File.exists?(::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json")) }
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
|
34
|
+
# end
|
35
|
+
|
36
|
+
# action :update do
|
37
|
+
|
38
|
+
# ip_address = new_resource.name
|
39
|
+
# target_registration_file_json = target_registration_file_to_json(new_resource)
|
40
|
+
# base_ssh_cluster_path = new_resource.ssh_cluster_path
|
41
|
+
|
42
|
+
# ChefMetal.inline_resource(self) do
|
43
|
+
# file ::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json") do
|
44
|
+
# content target_registration_file_json
|
45
|
+
# not_if { ::File.exists?(::File.join(Chef::Resource::SshCluster.path, "#{ip_address}.json")) }
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
50
|
+
# def load_current_resource
|
51
|
+
# end
|
52
|
+
|
53
|
+
# end
|
54
|
+
|
55
|
+
# def target_registration_file_to_json(new_resource)
|
56
|
+
|
57
|
+
# # Determine contents of registration file
|
58
|
+
# target_registration_file_content = {}
|
59
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'available' => new_resource.available })
|
60
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'ip_address' => new_resource.name })
|
61
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'machine_types' => new_resource.machine_types })
|
62
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'mac_address' => new_resource.mac_address }) #if new_resource.mac_address
|
63
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'hostname' => new_resource.hostname }) #if new_resource.hostname
|
64
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'password' => new_resource.password }) #if new_resource.hostname
|
65
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'key' => new_resource.key }) #if new_resource.hostname
|
66
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'subnet' => new_resource.subnet }) #if new_resource.subnet
|
67
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'domain' => new_resource.domain }) #if new_resource.domain
|
68
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'fqdn' => new_resource.fqdn }) #if new_resource.fqdn
|
69
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'memory' => new_resource.memory }) #if new_resource.memory
|
70
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'cpu_count' => new_resource.cpu_count }) #if new_resource.cpu_count
|
71
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'cpu_type' => new_resource.cpu_type }) #if new_resource.cpu_type
|
72
|
+
# target_registration_file_content = target_registration_file_content.merge!({ 'arch' => new_resource.arch }) #if new_resource.arch
|
73
|
+
|
74
|
+
# target_registration_file_json = JSON.parse(target_registration_file_content.to_json)
|
75
|
+
# target_registration_file_json_content = JSON.pretty_generate(target_registration_file_json)
|
76
|
+
# target_registration_file_json_content
|
77
|
+
|
78
|
+
# end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'chef/resource/lwrp_base'
|
2
|
+
require 'chef_metal_ssh'
|
3
|
+
|
4
|
+
class Chef::Resource::SshCluster < Chef::Resource::LWRPBase
|
5
|
+
self.resource_name = 'ssh_cluster'
|
6
|
+
|
7
|
+
actions :create, :delete, :nothing
|
8
|
+
default_action :create
|
9
|
+
|
10
|
+
attribute :path, :kind_of => String, :name_attribute => true
|
11
|
+
|
12
|
+
def after_created
|
13
|
+
super
|
14
|
+
run_context.chef_metal.with_driver "ssh:#{path}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# We are not interested in Chef's cloning behavior here.
|
18
|
+
def load_prior_resource
|
19
|
+
Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# require 'chef/resource/lwrp_base'
|
2
|
+
|
3
|
+
# class Chef::Resource::SshTarget < Chef::Resource::LWRPBase
|
4
|
+
|
5
|
+
# self.resource_name = 'ssh_target'
|
6
|
+
|
7
|
+
# actions :register, :update
|
8
|
+
|
9
|
+
# default_action :register
|
10
|
+
|
11
|
+
# attribute :ip_address,
|
12
|
+
# :kind_of => [String],
|
13
|
+
# :name => true
|
14
|
+
|
15
|
+
# # TODO, get path from cluster resource
|
16
|
+
# attribute :ssh_cluster_path,
|
17
|
+
# :kind_of => [String]
|
18
|
+
|
19
|
+
# attribute :mac_address,
|
20
|
+
# :kind_of => [String],
|
21
|
+
# :default => ""
|
22
|
+
|
23
|
+
# attribute :hostname,
|
24
|
+
# :kind_of => [String],
|
25
|
+
# :default => ""
|
26
|
+
|
27
|
+
# attribute :password,
|
28
|
+
# :kind_of => [String],
|
29
|
+
# :default => ""
|
30
|
+
|
31
|
+
# attribute :key,
|
32
|
+
# :kind_of => [String],
|
33
|
+
# :default => ""
|
34
|
+
|
35
|
+
# attribute :subnet,
|
36
|
+
# :kind_of => [String],
|
37
|
+
# :default => ""
|
38
|
+
|
39
|
+
# attribute :domain,
|
40
|
+
# :kind_of => [String],
|
41
|
+
# :default => ""
|
42
|
+
|
43
|
+
# attribute :fqdn,
|
44
|
+
# :kind_of => [String],
|
45
|
+
# :default => ""
|
46
|
+
|
47
|
+
# attribute :available,
|
48
|
+
# :kind_of => [String],
|
49
|
+
# :default => "true"
|
50
|
+
|
51
|
+
# attribute :machine_types,
|
52
|
+
# :kind_of => [Array],
|
53
|
+
# :default => Array.new
|
54
|
+
|
55
|
+
# attribute :memory,
|
56
|
+
# :kind_of => [String],
|
57
|
+
# :default => ""
|
58
|
+
|
59
|
+
# attribute :cpu_count,
|
60
|
+
# :kind_of => [String],
|
61
|
+
# :default => ""
|
62
|
+
|
63
|
+
# attribute :cpu_type,
|
64
|
+
# :kind_of => [String],
|
65
|
+
# :default => ""
|
66
|
+
|
67
|
+
# attribute :arch,
|
68
|
+
# :kind_of => [String],
|
69
|
+
# :default => ""
|
70
|
+
|
71
|
+
# end
|
data/lib/chef_metal_ssh.rb
CHANGED
@@ -1,2 +1,17 @@
|
|
1
1
|
require 'chef_metal'
|
2
|
-
require '
|
2
|
+
require 'chef/resource/ssh_cluster'
|
3
|
+
require 'chef/provider/ssh_cluster'
|
4
|
+
require 'chef/resource/ssh_target'
|
5
|
+
require 'chef/provider/ssh_target'
|
6
|
+
# require 'chef_metal_ssh/machine_registry'
|
7
|
+
require 'chef_metal_ssh/ssh_driver'
|
8
|
+
|
9
|
+
class Chef
|
10
|
+
module DSL
|
11
|
+
module Recipe
|
12
|
+
def with_ssh_cluster(cluster_path, &block)
|
13
|
+
with_driver("ssh:#{cluster_path}", &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# require 'chef_metal_ssh'
|
2
|
+
|
3
|
+
module ChefMetalSsh
|
4
|
+
module MachineRegistry
|
5
|
+
|
6
|
+
def validate_machine_options(node)
|
7
|
+
|
8
|
+
allowed_new_machine_keys = %w{
|
9
|
+
ssh_cluster_path
|
10
|
+
machine_types
|
11
|
+
mac_address
|
12
|
+
ip_address
|
13
|
+
subnet
|
14
|
+
hostname
|
15
|
+
domain
|
16
|
+
fqdn
|
17
|
+
memory
|
18
|
+
cpu_count
|
19
|
+
cpu_type
|
20
|
+
arch
|
21
|
+
}
|
22
|
+
|
23
|
+
# Validate Machine Options
|
24
|
+
new_machine.each { |k,v| raise 'Invalid Machine Option' unless allowed_new_machine_keys.include?(k) }
|
25
|
+
|
26
|
+
if new_machine['cpu_type'] && ! new_machine['cpu_type'].empty?
|
27
|
+
raise "Bad Cpu Type" unless ( new_machine['cpu_type'] == 'intel' || new_machine['cpu_type'] == 'amd' )
|
28
|
+
end
|
29
|
+
|
30
|
+
if new_machine['arch']
|
31
|
+
raise "No Such Arch. Either i386 or x86_64" unless ( new_machine['arch'] == 'i386' || new_machine['arch'] == 'x86_64' )
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def registered_machine_is_available?(v)
|
37
|
+
case v
|
38
|
+
when "true"
|
39
|
+
true
|
40
|
+
when "false"
|
41
|
+
false
|
42
|
+
when nil
|
43
|
+
true
|
44
|
+
else
|
45
|
+
raise "Available Key is not true or false string"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_provider_registration_file(action_handler, registry_file)
|
50
|
+
ChefMetal.inline_resource(action_handler) do
|
51
|
+
file registry_file do
|
52
|
+
action :delete
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_registration_file(action_handler, node, machine_options, new_machine_registry_match = false)
|
58
|
+
|
59
|
+
# if machine_options.has_key?("available") && machine_options["available"] == "false"
|
60
|
+
# raise ""
|
61
|
+
|
62
|
+
node['normal']['provisioner_options']['machine_options']['available'] = machine_options["available"]
|
63
|
+
|
64
|
+
machine_registration_file = ::File.join(Chef::Resource::SshCluster.path, "#{machine_options['ip_address']}.json")
|
65
|
+
|
66
|
+
if new_machine_registry_match
|
67
|
+
# delete_registry_file = ::File.join(Chef::Resource::SshCluster.path, "#{machine_options_json['ipaddress']}.json")
|
68
|
+
delete_provider_registration_file(action_handler, machine_registration_file)
|
69
|
+
end
|
70
|
+
|
71
|
+
machine_options_json = JSON.pretty_generate(machine_options)
|
72
|
+
|
73
|
+
ChefMetal.inline_resource(action_handler) do
|
74
|
+
file machine_registration_file do
|
75
|
+
content machine_options_json
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def match_machine_options_to_registered(ssh_cluster_path, machine_options)
|
82
|
+
|
83
|
+
ssh_cluster_machines = File.join(ssh_cluster_path, "*.json")
|
84
|
+
|
85
|
+
Dir.glob(ssh_cluster_machines).sort.each do |registered_machine_file|
|
86
|
+
|
87
|
+
# Not Available By Default.
|
88
|
+
# available_registered_machine = false
|
89
|
+
matched_machine_json = false unless matched_machine_json
|
90
|
+
|
91
|
+
ip_address_match = false unless ip_address_match
|
92
|
+
mac_address_match = false unless mac_address_match
|
93
|
+
fqdn_match = false unless fqdn_match
|
94
|
+
hostname_match = false unless hostname_match
|
95
|
+
node_name_match = false unless node_name_match
|
96
|
+
|
97
|
+
|
98
|
+
# Fail By Default.
|
99
|
+
will_work = false
|
100
|
+
not_gonna_work = false
|
101
|
+
# But Assume is Available till told its not
|
102
|
+
available_registered_machine = true unless (available_registered_machine == false)
|
103
|
+
|
104
|
+
registered_machine_json = JSON.parse(File.read(registered_machine_file))
|
105
|
+
|
106
|
+
ip_address_match = (registered_machine_json['ip_address'] == machine_options['ip_address']) # rescue false
|
107
|
+
mac_address_match = (registered_machine_json['mac_address'] == machine_options['mac_address']) # rescue false
|
108
|
+
fqdn_match = (registered_machine_json['fqdn'] == machine_options['fqdn']) # rescue false
|
109
|
+
hostname_match = (registered_machine_json['hostname'] == machine_options['hostname']) # rescue false
|
110
|
+
|
111
|
+
|
112
|
+
registered_machine_json.each_pair do |k,v|
|
113
|
+
|
114
|
+
# Check if key name is 'available' and if key value is true or false
|
115
|
+
if k == "available"
|
116
|
+
|
117
|
+
available_registered_machine =
|
118
|
+
registered_machine_is_available?(v) if available_registered_machine
|
119
|
+
|
120
|
+
elsif k == "node_name"
|
121
|
+
if v == machine_options[k] && (!v.nil? || !v.empty?)
|
122
|
+
node_name_match = true
|
123
|
+
end
|
124
|
+
else
|
125
|
+
if machine_options.has_key?(k)
|
126
|
+
case v
|
127
|
+
when String
|
128
|
+
# see if registered_machine value equals value in machine_options
|
129
|
+
if v == machine_options[k] && !v.empty?
|
130
|
+
will_work = true
|
131
|
+
else
|
132
|
+
not_gonna_work = true unless (v.empty? ||
|
133
|
+
machine_options[k].empty? ||
|
134
|
+
k == "password" )
|
135
|
+
end
|
136
|
+
when Array
|
137
|
+
Array(machine_options[k]).each do |sv|
|
138
|
+
if v.include?(sv)
|
139
|
+
will_work = true
|
140
|
+
else
|
141
|
+
not_gonna_work = true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
when Hash
|
145
|
+
else
|
146
|
+
Chef::Log.debug "NOTHING?"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# end
|
153
|
+
# else
|
154
|
+
# puts "already registered"
|
155
|
+
# end
|
156
|
+
|
157
|
+
#
|
158
|
+
# So we looped through a registered machine and:
|
159
|
+
#
|
160
|
+
# - we matched
|
161
|
+
#
|
162
|
+
# - we fatally matched
|
163
|
+
#
|
164
|
+
# - or we got nothin and move on to the next loop
|
165
|
+
#
|
166
|
+
|
167
|
+
if (will_work == true) && (not_gonna_work == false) && (available_registered_machine == true)
|
168
|
+
matched_machine_json = true
|
169
|
+
# break
|
170
|
+
end
|
171
|
+
|
172
|
+
error_out = false unless error_out
|
173
|
+
error_message = 'We Matched' unless error_message
|
174
|
+
if ip_address_match
|
175
|
+
error_out = true unless available_registered_machine
|
176
|
+
error_message << ' IP,'
|
177
|
+
end
|
178
|
+
|
179
|
+
if fqdn_match
|
180
|
+
error_out = true unless available_registered_machine
|
181
|
+
error_message << ' FQDN,'
|
182
|
+
end
|
183
|
+
|
184
|
+
if mac_address_match
|
185
|
+
error_out = true unless available_registered_machine
|
186
|
+
error_message << ' MAC ADDRESS,'
|
187
|
+
end
|
188
|
+
|
189
|
+
if hostname_match
|
190
|
+
error_out = true unless available_registered_machine
|
191
|
+
error_message << ' HOSTNAME,'
|
192
|
+
end
|
193
|
+
|
194
|
+
if node_name_match
|
195
|
+
error_out = true unless available_registered_machine
|
196
|
+
error_message << ' NODE NAME,'
|
197
|
+
end
|
198
|
+
|
199
|
+
if error_out && !available_registered_machine
|
200
|
+
error_message << ' but they already exist.'
|
201
|
+
error_message << ' Aborting to avoid inconsistencies.'
|
202
|
+
raise error_message
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# did we decide it will work?
|
207
|
+
if matched_machine_json
|
208
|
+
# Strip out any erroneous empty hash keys
|
209
|
+
# so we don't overwrite non-empty registered values
|
210
|
+
# with empty passed values
|
211
|
+
stripped_machine_json = JSON.parse(machine_options.to_json).delete_if {
|
212
|
+
|k, v| v.empty? unless k == 'machine_types' }
|
213
|
+
|
214
|
+
new_registration_json = registered_machine_json.merge!(stripped_machine_json)
|
215
|
+
|
216
|
+
# We're off the market
|
217
|
+
set_available_to_false = { "available" => "false" }
|
218
|
+
@matched_machine_json = new_registration_json.merge!(JSON.parse(set_available_to_false.to_json))
|
219
|
+
|
220
|
+
return @matched_machine_json
|
221
|
+
break
|
222
|
+
else
|
223
|
+
# wah wah wah
|
224
|
+
@matched_machine_json = false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
return @matched_machine_json
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'resolv'
|
3
|
+
require 'chef_metal/driver'
|
4
|
+
require 'chef_metal/version'
|
5
|
+
require 'chef_metal/machine/basic_machine'
|
6
|
+
require 'chef_metal/machine/unix_machine'
|
7
|
+
require 'chef_metal/convergence_strategy/install_cached'
|
8
|
+
require 'chef_metal/transport/ssh'
|
9
|
+
require 'chef_metal_ssh/machine_registry'
|
10
|
+
require 'chef_metal_ssh/version'
|
11
|
+
require 'chef/resource/ssh_cluster'
|
12
|
+
require 'chef/provider/ssh_cluster'
|
13
|
+
module ChefMetalSsh
|
14
|
+
# Provisions machines with ssh.
|
15
|
+
class SshDriver < ChefMetal::Driver
|
16
|
+
|
17
|
+
include ChefMetalSsh::MachineRegistry
|
18
|
+
|
19
|
+
# ## Parameters
|
20
|
+
# cluster_path - path to the directory containing the vagrant files, which
|
21
|
+
# should have been created with the vagrant_cluster resource.
|
22
|
+
|
23
|
+
# Create a new ssh driver.
|
24
|
+
#
|
25
|
+
# ## Parameters
|
26
|
+
# cluster_path - path to the directory containing the vagrant files, which
|
27
|
+
# should have been created with the vagrant_cluster resource.
|
28
|
+
def initialize(driver_url, config)
|
29
|
+
super
|
30
|
+
scheme, cluster_path = driver_url.split(':', 2)
|
31
|
+
@cluster_path = cluster_path
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :cluster_path
|
35
|
+
|
36
|
+
def self.from_url(driver_url, config)
|
37
|
+
SshDriver.new(driver_url, config)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.canonicalize_url(driver_url, config)
|
41
|
+
scheme, cluster_path = driver_url.split(':', 2)
|
42
|
+
cluster_path = File.expand_path(cluster_path || File.join(Chef::Config.config_dir, 'metal_ssh'))
|
43
|
+
"ssh:#{cluster_path}"
|
44
|
+
end
|
45
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
46
|
+
# object pointing at the machine, allowing useful actions like setup,
|
47
|
+
# converge, execute, file and directory. The Machine object will have a
|
48
|
+
# "node" property which must be saved to the server (if it is any
|
49
|
+
# different from the original node object).
|
50
|
+
#
|
51
|
+
# ## Parameters
|
52
|
+
# action_handler - the action_handler object that provides context.
|
53
|
+
# node - node object (deserialized json) representing this machine. If
|
54
|
+
# the node has a provisioner_options hash in it, these will be used
|
55
|
+
# instead of options provided by the provisioner. TODO compare and
|
56
|
+
# fail if different?
|
57
|
+
# node will have node['normal']['provisioner_options'] in it with any options.
|
58
|
+
# It is a hash with this format:
|
59
|
+
#
|
60
|
+
# -- provisioner_url: ssh:<@target_host>
|
61
|
+
# -- target_ip: the IP address of the target machine - IP or FQDN is required
|
62
|
+
# -- target_fqdn: The Resolvable name of the target machine - IP or FQDN is required
|
63
|
+
# -- ssh_user: the user to ssh as
|
64
|
+
# -- ssh_options: options to pass the ssh command. available options are here - https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh.rb#L61
|
65
|
+
#
|
66
|
+
# node['normal']['provisioner_output'] will be populated with information
|
67
|
+
# about the created machine. For ssh, it is a hash with this
|
68
|
+
# format:
|
69
|
+
#
|
70
|
+
# -- provisioner_url: ssh:<@target_host>
|
71
|
+
# -- name: container name
|
72
|
+
#
|
73
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
74
|
+
# TODO verify that the existing provisioner_url in the node is the same as ours
|
75
|
+
ensure_ssh_cluster(action_handler)
|
76
|
+
target_name = machine_spec.name
|
77
|
+
target_file_path = File.join(cluster_path, "#{machine_spec.name}.json")
|
78
|
+
|
79
|
+
|
80
|
+
# Chef::Log.debug("======================================>")
|
81
|
+
# Chef::Log.debug("acquire_machine - provisioner_options.inspect: #{provisioner_options.inspect}")
|
82
|
+
# Chef::Log.debug("======================================>")
|
83
|
+
|
84
|
+
@target_host = get_target_connection_method(machine_options)
|
85
|
+
|
86
|
+
# Chef::Log.debug("======================================>")
|
87
|
+
# Chef::Log.debug("acquire_machine - target_host: #{@target_host}")
|
88
|
+
# Chef::Log.debug("======================================>")
|
89
|
+
|
90
|
+
# Set up Provisioner Output
|
91
|
+
# TODO - make url the chef server url path? maybe disk path if zero?
|
92
|
+
machine_spec.location = {
|
93
|
+
'driver_url' => driver_url,
|
94
|
+
'driver_version' => ChefMetalSsh::VERSION,
|
95
|
+
'target_name' => target_name,
|
96
|
+
'target_file_path' => target_file_path,
|
97
|
+
'allocated_at' => Time.now.utc.to_s
|
98
|
+
}
|
99
|
+
|
100
|
+
# Chef::Log.debug("======================================>")
|
101
|
+
# Chef::Log.debug("acquire_machine - machine_spec.inspect: #{machine_spec.inspect}")
|
102
|
+
# Chef::Log.debug("======================================>")
|
103
|
+
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
108
|
+
machine_for(machine_spec, machine_options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def connect_to_machine(machine_spec, machine_options)
|
112
|
+
machine_for(machine_spec, machine_options)
|
113
|
+
end
|
114
|
+
# # Connect to machine without acquiring it
|
115
|
+
# def connect_to_machine(node)
|
116
|
+
|
117
|
+
# # Get Password If Needs To Be Got
|
118
|
+
# provisioner_url = node['normal']['provisioner_output']['provisioner_url']
|
119
|
+
# provisioner_path = provisioner_url.split(':', 2)[1].sub(/^\/\//, "")
|
120
|
+
# existing_machine_options = JSON.parse(File.read(provisioner_path)) # rescue nil
|
121
|
+
# node_machine_options = node['normal']['provisioner_options']['machine_options']
|
122
|
+
# unless node_machine_options['password']
|
123
|
+
# Chef::Log.debug "Password Not in Provisioner Machine Options"
|
124
|
+
# node_machine_options['password'] = existing_machine_options['password'] ?
|
125
|
+
# existing_machine_options['password'] : nil
|
126
|
+
# end
|
127
|
+
|
128
|
+
# # Get Some
|
129
|
+
# machine_for(machine_spec, machine_options)
|
130
|
+
# end
|
131
|
+
|
132
|
+
# def delete_machine(action_handler, node)
|
133
|
+
# convergence_strategy_for(node).delete_chef_objects(action_handler, node)
|
134
|
+
# end
|
135
|
+
|
136
|
+
# def stop_machine(action_handler, node)
|
137
|
+
# #
|
138
|
+
# # What to do What to do.
|
139
|
+
# #
|
140
|
+
# # On one level there's really only one thing to do here,
|
141
|
+
# # shellout and halt, or shutdown -h now,
|
142
|
+
# # maybe provide abitily to pass some shutdown options
|
143
|
+
# #
|
144
|
+
# # But be vewwy vewwy careful:
|
145
|
+
# #
|
146
|
+
# # you better have console...
|
147
|
+
# # or be close to your datacenter
|
148
|
+
# #
|
149
|
+
# end
|
150
|
+
|
151
|
+
# def restart_machine(action_handler, node)
|
152
|
+
# # Full Restart, POST BIOS and all
|
153
|
+
# end
|
154
|
+
|
155
|
+
# def reload_machine(action_handler, node)
|
156
|
+
# # Use `kexec` here to skip POST and BIOS and all that noise.
|
157
|
+
# end
|
158
|
+
|
159
|
+
def driver_url
|
160
|
+
"ssh:#{cluster_path}"
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
protected
|
165
|
+
|
166
|
+
def ensure_ssh_cluster(action_handler)
|
167
|
+
_cluster_path = cluster_path
|
168
|
+
ChefMetal.inline_resource(action_handler) do
|
169
|
+
ssh_cluster _cluster_path
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_target_connection_method(given_machine_options)
|
174
|
+
|
175
|
+
machine_options = symbolize_keys(given_machine_options)
|
176
|
+
|
177
|
+
target_ip = machine_options[:ip_address] || false
|
178
|
+
target_fqdn = machine_options[:fqdn] || false
|
179
|
+
|
180
|
+
raise "no @target_host, target_ip or target_fqdn given" unless
|
181
|
+
( @target_host || target_ip || target_fqdn )
|
182
|
+
|
183
|
+
remote_host = ''
|
184
|
+
if @target_host
|
185
|
+
remote_host = @target_host
|
186
|
+
elsif target_ip
|
187
|
+
raise 'Invalid IP' unless ( target_ip =~ Resolv::IPv4::Regex ||
|
188
|
+
target_ip =~ Resolv::IPv6::Regex )
|
189
|
+
remote_host = target_ip
|
190
|
+
elsif target_fqdn
|
191
|
+
rh = Resolv::Hosts.new
|
192
|
+
rd = Resolv.new
|
193
|
+
|
194
|
+
begin
|
195
|
+
rh.getaddress(target_fqdn)
|
196
|
+
in_hosts_file = true
|
197
|
+
rescue
|
198
|
+
in_hosts_file = false
|
199
|
+
end
|
200
|
+
|
201
|
+
begin
|
202
|
+
rd.getaddress(target_fqdn)
|
203
|
+
in_dns = true
|
204
|
+
rescue
|
205
|
+
in_dns = false
|
206
|
+
end
|
207
|
+
|
208
|
+
raise 'Unresolvable Hostname' unless ( in_hosts_file || in_dns )
|
209
|
+
remote_host = target_fqdn
|
210
|
+
else
|
211
|
+
raise "aint got no target yo, that dog dont hunt"
|
212
|
+
end
|
213
|
+
|
214
|
+
Chef::Log.debug("======================================>")
|
215
|
+
Chef::Log.debug("get_target_connection_method - remote_host: #{remote_host}")
|
216
|
+
Chef::Log.debug("======================================>")
|
217
|
+
|
218
|
+
remote_host
|
219
|
+
end
|
220
|
+
|
221
|
+
def machine_for(machine_spec, machine_options)
|
222
|
+
# ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
|
223
|
+
ChefMetal::Machine::UnixMachine.new(machine_spec,
|
224
|
+
create_ssh_transport(machine_options),
|
225
|
+
convergence_strategy_for(machine_spec, machine_options))
|
226
|
+
end
|
227
|
+
|
228
|
+
def transport_for(machine_options)
|
229
|
+
create_ssh_transport(machine_options)
|
230
|
+
end
|
231
|
+
|
232
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
233
|
+
@unix_convergence_strategy ||= begin
|
234
|
+
ChefMetal::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options],
|
235
|
+
config)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def symbolize_keys(hash)
|
240
|
+
hash.inject({}){|result, (key, value)|
|
241
|
+
|
242
|
+
new_key = case key
|
243
|
+
when String
|
244
|
+
key.to_sym
|
245
|
+
else
|
246
|
+
key
|
247
|
+
end
|
248
|
+
|
249
|
+
new_value = case value
|
250
|
+
when Hash
|
251
|
+
symbolize_keys(value)
|
252
|
+
else
|
253
|
+
value
|
254
|
+
end
|
255
|
+
|
256
|
+
result[new_key] = new_value
|
257
|
+
result
|
258
|
+
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
# Setup Ssh
|
263
|
+
def create_ssh_transport(machine_options)
|
264
|
+
machine_ssh_options = machine_options['ssh_options']
|
265
|
+
|
266
|
+
##
|
267
|
+
# Ssh Username
|
268
|
+
username = machine_ssh_options['user'] || 'root'
|
269
|
+
|
270
|
+
Chef::Log.debug("======================================>")
|
271
|
+
Chef::Log.debug("create_ssh_transport - username: #{username}")
|
272
|
+
Chef::Log.debug("======================================>")
|
273
|
+
|
274
|
+
##
|
275
|
+
# Ssh Password
|
276
|
+
ssh_pass = machine_ssh_options['password'] || false
|
277
|
+
if ssh_pass
|
278
|
+
ssh_pass_hash = Hash.new
|
279
|
+
ssh_pass_hash = { 'password' => ssh_pass }
|
280
|
+
else
|
281
|
+
Chef::Log.info("NO PASSWORD")
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Ssh Key
|
286
|
+
ssh_keys = []
|
287
|
+
if machine_ssh_options['keys']
|
288
|
+
if machine_ssh_options['keys'].kind_of?(Array)
|
289
|
+
machine_ssh_options['keys'].each do |key|
|
290
|
+
ssh_keys << key
|
291
|
+
end
|
292
|
+
elsif machine_ssh_options['keys'].kind_of?(String)
|
293
|
+
ssh_keys << machine_ssh_options['keys']
|
294
|
+
else
|
295
|
+
ssh_keys = false
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
if ssh_keys
|
300
|
+
ssh_key_hash = Hash.new
|
301
|
+
ssh_key_hash = { 'keys' => ssh_keys }
|
302
|
+
end
|
303
|
+
|
304
|
+
Chef::Log.info("======================================>")
|
305
|
+
if ssh_pass
|
306
|
+
Chef::Log.info("create_ssh_transport - ssh_pass: #{ssh_pass_hash.inspect}")
|
307
|
+
elsif ssh_keys
|
308
|
+
Chef::Log.info("create_ssh_transport - ssh_key: #{ssh_keys.inpsect}")
|
309
|
+
else
|
310
|
+
Chef::Log.info("create_ssh_transport - no ssh_pass or ssh_key given")
|
311
|
+
end
|
312
|
+
Chef::Log.info("======================================>")
|
313
|
+
|
314
|
+
raise "no ssh_pass or ssh_key given" unless ( ssh_pass || ssh_keys )
|
315
|
+
|
316
|
+
machine_ssh_options = machine_ssh_options.merge!(ssh_pass_hash)
|
317
|
+
machine_ssh_options = machine_ssh_options.merge!(ssh_key_hash)
|
318
|
+
|
319
|
+
##
|
320
|
+
# Valid Ssh Options
|
321
|
+
valid_ssh_options = [
|
322
|
+
:auth_methods, :bind_address, :compression, :compression_level, :config,
|
323
|
+
:encryption, :forward_agent, :hmac, :host_key,
|
324
|
+
:keepalive, :keepalive_interval, :kex, :keys, :key_data,
|
325
|
+
:languages, :logger, :paranoid, :password, :port, :proxy,
|
326
|
+
:rekey_blocks_limit,:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
|
327
|
+
:global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
|
328
|
+
:host_name, :user, :properties, :passphrase, :keys_only, :max_pkt_size,
|
329
|
+
:max_win_size, :send_env, :use_agent
|
330
|
+
]
|
331
|
+
|
332
|
+
##
|
333
|
+
# Ssh Options
|
334
|
+
ssh_options = symbolize_keys(machine_ssh_options)
|
335
|
+
|
336
|
+
# Validate Ssh Options
|
337
|
+
ssh_options.each { |k,v| raise 'Invalid Shh Option' unless valid_ssh_options.include?(k) }
|
338
|
+
|
339
|
+
Chef::Log.debug "======================================>"
|
340
|
+
Chef::Log.debug "create_ssh_transport - ssh_options: #{ssh_options.inspect}"
|
341
|
+
Chef::Log.debug "======================================>"
|
342
|
+
|
343
|
+
# Now That We Validated Options, Lets Get Our Target
|
344
|
+
@target_host = get_target_connection_method(machine_ssh_options)
|
345
|
+
|
346
|
+
# Make Sure We Can Connect
|
347
|
+
begin
|
348
|
+
ssh = Net::SSH.start(@target_host, username, ssh_options)
|
349
|
+
ssh.close
|
350
|
+
Chef::Log.debug("======================================>")
|
351
|
+
Chef::Log.debug("ABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}")
|
352
|
+
Chef::Log.debug("======================================>")
|
353
|
+
rescue
|
354
|
+
Chef::Log.debug("======================================>")
|
355
|
+
Chef::Log.debug("UNABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}")
|
356
|
+
Chef::Log.debug("======================================>")
|
357
|
+
raise "UNABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}"
|
358
|
+
end
|
359
|
+
|
360
|
+
##
|
361
|
+
# Ssh Additional Options
|
362
|
+
options = {}
|
363
|
+
|
364
|
+
#Enable pty by default
|
365
|
+
options[:ssh_pty_enable] = true
|
366
|
+
|
367
|
+
# If we not root use sudo
|
368
|
+
if username != 'root'
|
369
|
+
options[:prefix] = 'sudo '
|
370
|
+
end
|
371
|
+
|
372
|
+
Chef::Log.debug("======================================>")
|
373
|
+
Chef::Log.debug("create_ssh_transport - options: #{options.inspect}")
|
374
|
+
Chef::Log.debug("======================================>")
|
375
|
+
|
376
|
+
ChefMetal::Transport::SSH.new(@target_host, username, ssh_options, options, config)
|
377
|
+
|
378
|
+
# We Duped It So Now We Can Zero the Node Attr. So Not Saved On Server
|
379
|
+
# provisioner_options['machine_options']['password'] =
|
380
|
+
# nil if provisioner_options['machine_options']['password']
|
381
|
+
|
382
|
+
# provisioner_options['ssh_options']['password'] =
|
383
|
+
# nil if provisioner_options['ssh_options']['password']
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-metal-ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-10-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
16
|
-
requirement: &
|
16
|
+
requirement: &22050800 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,21 +21,21 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *22050800
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: chef-metal
|
27
|
-
requirement: &
|
27
|
+
requirement: &22049540 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0.
|
32
|
+
version: '0.12'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *22049540
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bundler
|
38
|
-
requirement: &
|
38
|
+
requirement: &22068960 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '1.5'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *22068960
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &22068460 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *22068460
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rake
|
60
|
-
requirement: &
|
60
|
+
requirement: &22067540 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *22067540
|
69
69
|
description: Provisioner for managing servers using ssh in Chef Metal.
|
70
70
|
email: zackzondlo@gmail.com
|
71
71
|
executables: []
|
@@ -77,10 +77,15 @@ files:
|
|
77
77
|
- Rakefile
|
78
78
|
- LICENSE.txt
|
79
79
|
- README.md
|
80
|
-
- lib/
|
80
|
+
- lib/chef/provider/ssh_cluster.rb
|
81
|
+
- lib/chef/provider/ssh_target.rb
|
82
|
+
- lib/chef/resource/ssh_cluster.rb
|
83
|
+
- lib/chef/resource/ssh_target.rb
|
84
|
+
- lib/chef_metal/driver_init/ssh.rb
|
81
85
|
- lib/chef_metal_ssh.rb
|
82
|
-
- lib/chef_metal_ssh/ssh_provisioner.rb
|
83
86
|
- lib/chef_metal_ssh/version.rb
|
87
|
+
- lib/chef_metal_ssh/ssh_driver.rb
|
88
|
+
- lib/chef_metal_ssh/machine_registry.rb
|
84
89
|
homepage: https://github.com/double-z/chef-metal-ssh
|
85
90
|
licenses: []
|
86
91
|
post_install_message:
|
@@ -1,300 +0,0 @@
|
|
1
|
-
require 'resolv'
|
2
|
-
require 'chef_metal/provisioner'
|
3
|
-
require 'chef_metal/version'
|
4
|
-
require 'chef_metal/machine/basic_machine'
|
5
|
-
require 'chef_metal/machine/unix_machine'
|
6
|
-
require 'chef_metal/convergence_strategy/install_cached'
|
7
|
-
require 'chef_metal/transport/ssh'
|
8
|
-
|
9
|
-
module ChefMetalSsh
|
10
|
-
# Provisions machines with ssh.
|
11
|
-
class SshProvisioner < ChefMetal::Provisioner
|
12
|
-
|
13
|
-
def initialize()
|
14
|
-
end
|
15
|
-
|
16
|
-
# Acquire a machine, generally by provisioning it. Returns a Machine
|
17
|
-
# object pointing at the machine, allowing useful actions like setup,
|
18
|
-
# converge, execute, file and directory. The Machine object will have a
|
19
|
-
# "node" property which must be saved to the server (if it is any
|
20
|
-
# different from the original node object).
|
21
|
-
#
|
22
|
-
# ## Parameters
|
23
|
-
# action_handler - the action_handler object that provides context.
|
24
|
-
# node - node object (deserialized json) representing this machine. If
|
25
|
-
# the node has a provisioner_options hash in it, these will be used
|
26
|
-
# instead of options provided by the provisioner. TODO compare and
|
27
|
-
# fail if different?
|
28
|
-
# node will have node['normal']['provisioner_options'] in it with any options.
|
29
|
-
# It is a hash with this format:
|
30
|
-
#
|
31
|
-
# -- provisioner_url: ssh:<@target_host>
|
32
|
-
# -- target_ip: the IP address of the target machine - IP or FQDN is required
|
33
|
-
# -- target_fqdn: The Resolvable name of the target machine - IP or FQDN is required
|
34
|
-
# -- ssh_user: the user to ssh as
|
35
|
-
# -- ssh_options: options to pass the ssh command. available options are here - https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh.rb#L61
|
36
|
-
#
|
37
|
-
# node['normal']['provisioner_output'] will be populated with information
|
38
|
-
# about the created machine. For ssh, it is a hash with this
|
39
|
-
# format:
|
40
|
-
#
|
41
|
-
# -- provisioner_url: ssh:<@target_host>
|
42
|
-
# -- name: container name
|
43
|
-
#
|
44
|
-
def acquire_machine(action_handler, node)
|
45
|
-
# TODO verify that the existing provisioner_url in the node is the same as ours
|
46
|
-
|
47
|
-
# Set up the modified node data
|
48
|
-
provisioner_options = node['normal']['provisioner_options']
|
49
|
-
|
50
|
-
Chef::Log.debug("======================================>")
|
51
|
-
Chef::Log.debug("acquire_machine - provisioner_options.inspect: #{provisioner_options.inspect}")
|
52
|
-
Chef::Log.debug("======================================>")
|
53
|
-
|
54
|
-
@target_host = get_target_connection_method(node)
|
55
|
-
|
56
|
-
Chef::Log.debug("======================================>")
|
57
|
-
Chef::Log.debug("acquire_machine - target_host: #{@target_host}")
|
58
|
-
Chef::Log.debug("======================================>")
|
59
|
-
|
60
|
-
# Set up Provisioner Output
|
61
|
-
# TODO - make url the chef server url path? maybe disk path if zero?
|
62
|
-
provisioner_output = node['normal']['provisioner_output'] || {
|
63
|
-
'provisioner_url' => "ssh:#{@target_host}",
|
64
|
-
'name' => node['name']
|
65
|
-
}
|
66
|
-
|
67
|
-
Chef::Log.debug("======================================>")
|
68
|
-
Chef::Log.debug("acquire_machine - provisioner_output.inspect: #{provisioner_output.inspect}")
|
69
|
-
Chef::Log.debug("======================================>")
|
70
|
-
|
71
|
-
node['normal']['provisioner_output'] = provisioner_output
|
72
|
-
|
73
|
-
# Create machine object for callers to use
|
74
|
-
machine_for(node)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Connect to machine without acquiring it
|
78
|
-
def connect_to_machine(node)
|
79
|
-
@target_host = get_target_connection_method(node)
|
80
|
-
|
81
|
-
Chef::Log.debug("======================================>")
|
82
|
-
Chef::Log.debug("connect_to_machine - target_host: #{@target_host}")
|
83
|
-
Chef::Log.debug("======================================>")
|
84
|
-
|
85
|
-
machine_for(node)
|
86
|
-
end
|
87
|
-
|
88
|
-
def delete_machine(action_handler, node)
|
89
|
-
convergence_strategy_for(node).delete_chef_objects(action_handler, node)
|
90
|
-
end
|
91
|
-
|
92
|
-
def stop_machine(action_handler, node)
|
93
|
-
# What to do What to do.
|
94
|
-
# On one level there's really only one thing to do here,
|
95
|
-
# shellout and halt, or shutdown -h now,
|
96
|
-
# maybe provide abitily to pass some shutdown options
|
97
|
-
# But be vewwy vewwy careful, you better have console,
|
98
|
-
# or be close to your datacenter
|
99
|
-
true
|
100
|
-
end
|
101
|
-
|
102
|
-
def restart_machine(action_handler, node)
|
103
|
-
# Full Restart, POST BIOS and all
|
104
|
-
end
|
105
|
-
|
106
|
-
def reload_machine(action_handler, node)
|
107
|
-
# Use `kexec` here to skip POST and BIOS and all that noise.
|
108
|
-
end
|
109
|
-
|
110
|
-
# Not meant to be part of public interface
|
111
|
-
def transport_for(node)
|
112
|
-
create_ssh_transport(node)
|
113
|
-
end
|
114
|
-
|
115
|
-
protected
|
116
|
-
|
117
|
-
def get_target_connection_method(node)
|
118
|
-
|
119
|
-
provisioner_options = node['normal']['provisioner_options']
|
120
|
-
|
121
|
-
target_ip = ''
|
122
|
-
target_ip = provisioner_options['target_ip'] || nil
|
123
|
-
|
124
|
-
target_fqdn = ''
|
125
|
-
target_fqdn = provisioner_options['target_fqdn'] || nil
|
126
|
-
|
127
|
-
remote_host = ''
|
128
|
-
if @target_host
|
129
|
-
remote_host = @target_host
|
130
|
-
elsif target_ip
|
131
|
-
raise 'Invalid IP' unless ( target_ip =~ Resolv::IPv4::Regex ||
|
132
|
-
target_ip =~ Resolv::IPv6::Regex )
|
133
|
-
remote_host = target_ip
|
134
|
-
elsif target_fqdn
|
135
|
-
rh = Resolv::Hosts.new
|
136
|
-
rd = Resolv.new
|
137
|
-
|
138
|
-
begin
|
139
|
-
rh.getaddress(target_fqdn)
|
140
|
-
in_hosts_file = true
|
141
|
-
rescue
|
142
|
-
in_hosts_file = false
|
143
|
-
end
|
144
|
-
|
145
|
-
begin
|
146
|
-
rd.getaddress(target_fqdn)
|
147
|
-
in_dns = true
|
148
|
-
rescue
|
149
|
-
in_dns = false
|
150
|
-
end
|
151
|
-
|
152
|
-
raise 'Unresolvable Hostname' unless ( in_hosts_file || in_dns )
|
153
|
-
remote_host = target_fqdn
|
154
|
-
else
|
155
|
-
raise "aint got no target yo, that dog dont hunt"
|
156
|
-
end
|
157
|
-
|
158
|
-
Chef::Log.debug("======================================>")
|
159
|
-
Chef::Log.debug("get_target_connection_method - remote_host: #{remote_host}")
|
160
|
-
Chef::Log.debug("======================================>")
|
161
|
-
|
162
|
-
remote_host
|
163
|
-
end
|
164
|
-
|
165
|
-
def machine_for(node)
|
166
|
-
ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
|
167
|
-
end
|
168
|
-
|
169
|
-
def convergence_strategy_for(node)
|
170
|
-
@convergence_strategy ||= begin
|
171
|
-
ChefMetal::ConvergenceStrategy::InstallCached.new
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def symbolize_keys(hash)
|
176
|
-
hash.inject({}){|result, (key, value)|
|
177
|
-
new_key = case key
|
178
|
-
when String then key.to_sym
|
179
|
-
else key
|
180
|
-
end
|
181
|
-
new_value = case value
|
182
|
-
when Hash then symbolize_keys(value)
|
183
|
-
else value
|
184
|
-
end
|
185
|
-
result[new_key] = new_value
|
186
|
-
result
|
187
|
-
}
|
188
|
-
end
|
189
|
-
|
190
|
-
# Setup Ssh
|
191
|
-
def create_ssh_transport(node)
|
192
|
-
|
193
|
-
provisioner_options = node['normal']['provisioner_options']
|
194
|
-
provisioner_ssh_options = provisioner_options['ssh_options']
|
195
|
-
|
196
|
-
Chef::Log.debug("======================================>")
|
197
|
-
Chef::Log.debug("create_ssh_transport - target_host: #{@target_host}")
|
198
|
-
Chef::Log.debug("======================================>")
|
199
|
-
|
200
|
-
##
|
201
|
-
# Ssh Username
|
202
|
-
username = ''
|
203
|
-
username = provisioner_options['ssh_user'] || 'vagrant'
|
204
|
-
|
205
|
-
Chef::Log.debug("======================================>")
|
206
|
-
Chef::Log.debug("create_ssh_transport - username: #{username}")
|
207
|
-
Chef::Log.debug("======================================>")
|
208
|
-
|
209
|
-
##
|
210
|
-
# Ssh Password
|
211
|
-
ssh_pass = false
|
212
|
-
ssh_pass = provisioner_ssh_options['password'] if provisioner_ssh_options['password']
|
213
|
-
# ssh_pass = ssh_options[:password] if ssh_options[:password]
|
214
|
-
|
215
|
-
##
|
216
|
-
# Ssh Key
|
217
|
-
ssh_key = false
|
218
|
-
ssh_key = provisioner_ssh_options['host_key'] if provisioner_ssh_options['host_key']
|
219
|
-
|
220
|
-
Chef::Log.debug("======================================>")
|
221
|
-
if ssh_pass
|
222
|
-
Chef::Log.debug("create_ssh_transport - ssh_pass: #{ssh_pass}")
|
223
|
-
elsif ssh_key
|
224
|
-
Chef::Log.debug("create_ssh_transport - ssh_key: #{ssh_key}")
|
225
|
-
else
|
226
|
-
Chef::Log.debug("create_ssh_transport - no ssh_pass or ssh_key given")
|
227
|
-
end
|
228
|
-
Chef::Log.debug("======================================>")
|
229
|
-
|
230
|
-
raise "no ssh_pass or ssh_key given" unless ( ssh_pass || ssh_key )
|
231
|
-
##
|
232
|
-
# Ssh Main Options
|
233
|
-
valid_ssh_options = [
|
234
|
-
:auth_methods, :bind_address, :compression, :compression_level, :config,
|
235
|
-
:encryption, :forward_agent, :hmac, :host_key,
|
236
|
-
:keepalive, :keepalive_interval, :kex, :keys, :key_data,
|
237
|
-
:languages, :logger, :paranoid, :password, :port, :proxy,
|
238
|
-
:rekey_blocks_limit,:rekey_limit, :rekey_packet_limit, :timeout, :verbose,
|
239
|
-
:global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
|
240
|
-
:host_name, :user, :properties, :passphrase, :keys_only, :max_pkt_size,
|
241
|
-
:max_win_size, :send_env, :use_agent
|
242
|
-
]
|
243
|
-
|
244
|
-
##
|
245
|
-
# Ssh Main Options
|
246
|
-
ssh_options = symbolize_keys(provisioner_ssh_options)
|
247
|
-
|
248
|
-
# Validate Ssh Options
|
249
|
-
ssh_options.each { |k,v| raise 'Invalid Shh Option' unless valid_ssh_options.include?(k) }
|
250
|
-
|
251
|
-
##
|
252
|
-
# Ssh Main Options
|
253
|
-
# ssh_options = symbolize_keys(provisioner_ssh_options)
|
254
|
-
# ssh_options = {
|
255
|
-
# # TODO create a user known hosts file
|
256
|
-
# # :user_known_hosts_file => provisioner_options['ssh_connect_options']['UserKnownHostsFile'],
|
257
|
-
# # :paranoid => true,
|
258
|
-
# # :auth_methods => [ 'publickey' ],
|
259
|
-
# :keys_only => false,
|
260
|
-
# :host_key => ssh_key,
|
261
|
-
# :password => ssh_pass
|
262
|
-
# }
|
263
|
-
|
264
|
-
Chef::Log.debug("======================================>")
|
265
|
-
Chef::Log.debug("create_ssh_transport - ssh_options: #{ssh_options.inspect}")
|
266
|
-
Chef::Log.debug("======================================>")
|
267
|
-
|
268
|
-
# Make Sure We Can Connect
|
269
|
-
begin
|
270
|
-
ssh = Net::SSH.start(@target_host, username, ssh_options)
|
271
|
-
ssh.close
|
272
|
-
Chef::Log.debug("======================================>")
|
273
|
-
Chef::Log.debug("ABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}")
|
274
|
-
Chef::Log.debug("======================================>")
|
275
|
-
rescue
|
276
|
-
Chef::Log.debug("======================================>")
|
277
|
-
Chef::Log.debug("UNABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}")
|
278
|
-
Chef::Log.debug("======================================>")
|
279
|
-
raise "UNABLE to Connect to #{@target_host} using #{username} and #{ssh_options.inspect}"
|
280
|
-
end
|
281
|
-
|
282
|
-
##
|
283
|
-
# Ssh Additional Options
|
284
|
-
options = {}
|
285
|
-
#Enable pty by default
|
286
|
-
options[:ssh_pty_enable] = true
|
287
|
-
|
288
|
-
if username != 'root'
|
289
|
-
options[:prefix] = 'sudo '
|
290
|
-
end
|
291
|
-
|
292
|
-
Chef::Log.debug("======================================>")
|
293
|
-
Chef::Log.debug("create_ssh_transport - options: #{options.inspect}")
|
294
|
-
Chef::Log.debug("======================================>")
|
295
|
-
|
296
|
-
ChefMetal::Transport::SSH.new(@target_host, username, ssh_options, options)
|
297
|
-
end
|
298
|
-
|
299
|
-
end
|
300
|
-
end
|