chef-metal 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +160 -0
- data/Rakefile +6 -0
- data/lib/chef/provider/fog_key_pair.rb +106 -0
- data/lib/chef/provider/machine.rb +60 -0
- data/lib/chef/provider/machine_file.rb +39 -0
- data/lib/chef/provider/vagrant_box.rb +44 -0
- data/lib/chef/provider/vagrant_cluster.rb +39 -0
- data/lib/chef/resource/fog_key_pair.rb +34 -0
- data/lib/chef/resource/machine.rb +56 -0
- data/lib/chef/resource/machine_file.rb +25 -0
- data/lib/chef/resource/vagrant_box.rb +18 -0
- data/lib/chef/resource/vagrant_cluster.rb +16 -0
- data/lib/chef_metal.rb +84 -0
- data/lib/chef_metal/aws_credentials.rb +55 -0
- data/lib/chef_metal/convergence_strategy.rb +15 -0
- data/lib/chef_metal/convergence_strategy/install_msi.rb +41 -0
- data/lib/chef_metal/convergence_strategy/install_sh.rb +36 -0
- data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +140 -0
- data/lib/chef_metal/inline_resource.rb +88 -0
- data/lib/chef_metal/machine.rb +79 -0
- data/lib/chef_metal/machine/basic_machine.rb +79 -0
- data/lib/chef_metal/machine/unix_machine.rb +108 -0
- data/lib/chef_metal/machine/windows_machine.rb +94 -0
- data/lib/chef_metal/provisioner.rb +71 -0
- data/lib/chef_metal/provisioner/fog_provisioner.rb +378 -0
- data/lib/chef_metal/provisioner/vagrant_provisioner.rb +327 -0
- data/lib/chef_metal/recipe_dsl.rb +26 -0
- data/lib/chef_metal/transport.rb +36 -0
- data/lib/chef_metal/transport/ssh.rb +157 -0
- data/lib/chef_metal/transport/winrm.rb +91 -0
- data/lib/chef_metal/version.rb +3 -0
- metadata +175 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module ChefMetal
|
2
|
+
class ConvergenceStrategy
|
3
|
+
def setup_convergence(provider, machine, machine_resource)
|
4
|
+
raise "setup_convergence not overridden on #{self.class}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def converge(provider, machine)
|
8
|
+
raise "converge not overridden on #{self.class}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete_chef_objects(provider, node)
|
12
|
+
raise "delete_chef_objects not overridden on #{self.class}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'chef_metal/convergence_strategy/precreate_chef_objects'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module ChefMetal
|
5
|
+
class ConvergenceStrategy
|
6
|
+
class InstallMsi < PrecreateChefObjects
|
7
|
+
@@install_msi_cache = {}
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@install_msi_url = options[:install_msi_url] || 'http://www.opscode.com/chef/install.msi'
|
11
|
+
@install_msi_path = options[:install_msi_path] || "%TEMP%\\#{File.basename(@install_msi_url)}"
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :install_msi_url
|
15
|
+
attr_reader :install_msi_path
|
16
|
+
|
17
|
+
def setup_convergence(provider, machine, machine_resource)
|
18
|
+
system_drive = machine.execute_always('$env:SystemDrive').stdout.strip
|
19
|
+
@client_rb_path ||= "#{system_drive}\\chef\\client.rb"
|
20
|
+
@client_pem_path ||= "#{system_drive}\\chef\\client.pem"
|
21
|
+
|
22
|
+
super
|
23
|
+
|
24
|
+
# Install chef-client. TODO check and update version if not latest / not desired
|
25
|
+
if machine.execute_always('chef-client -v').exitstatus != 0
|
26
|
+
# TODO ssh verification of install.sh before running arbtrary code would be nice?
|
27
|
+
# TODO find a way to cache this on the host like with the Unix stuff.
|
28
|
+
# Limiter is we don't know how to efficiently upload large files to
|
29
|
+
# the remote machine with WMI.
|
30
|
+
machine.execute(provider, "(New-Object System.Net.WebClient).DownloadFile(#{machine.escape(install_msi_url)}, #{machine.escape(install_msi_path)})")
|
31
|
+
machine.execute(provider, "msiexec /qn /i #{machine.escape(install_msi_path)}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def converge(provider, machine)
|
36
|
+
# TODO For some reason I get a 500 back if I don't do -l debug
|
37
|
+
machine.transport.execute("chef-client -l debug")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'chef_metal/convergence_strategy/precreate_chef_objects'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module ChefMetal
|
5
|
+
class ConvergenceStrategy
|
6
|
+
class InstallSh < PrecreateChefObjects
|
7
|
+
@@install_sh_cache = {}
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@install_sh_url = options[:install_sh_url] || 'http://www.opscode.com/chef/install.sh'
|
11
|
+
@install_sh_path = options[:install_sh_path] || '/tmp/chef-install.sh'
|
12
|
+
@client_rb_path ||= '/etc/chef/client.rb'
|
13
|
+
@client_pem_path ||= '/etc/chef/client.pem'
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :install_sh_url
|
17
|
+
attr_reader :install_sh_path
|
18
|
+
|
19
|
+
def setup_convergence(provider, machine, machine_resource)
|
20
|
+
super
|
21
|
+
|
22
|
+
# Install chef-client. TODO check and update version if not latest / not desired
|
23
|
+
if machine.execute_always('chef-client -v').exitstatus != 0
|
24
|
+
# TODO ssh verification of install.sh before running arbtrary code would be nice?
|
25
|
+
@@install_sh_cache[install_sh_url] ||= Net::HTTP.get(URI(install_sh_url))
|
26
|
+
machine.write_file(provider, install_sh_path, @@install_sh_cache[install_sh_url], :ensure_dir => true)
|
27
|
+
machine.execute(provider, "bash #{install_sh_path}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def converge(provider, machine)
|
32
|
+
machine.execute(provider, 'chef-client')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'chef_metal/convergence_strategy'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module ChefMetal
|
5
|
+
class ConvergenceStrategy
|
6
|
+
class PrecreateChefObjects < ConvergenceStrategy
|
7
|
+
def initialize(options = {})
|
8
|
+
super
|
9
|
+
@client_rb_path = options[:client_rb_path]
|
10
|
+
@client_pem_path = options[:client_pem_path]
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :client_rb_path
|
14
|
+
attr_reader :client_pem_path
|
15
|
+
|
16
|
+
def setup_convergence(provider, machine, machine_resource)
|
17
|
+
# Create keys on machine
|
18
|
+
public_key = create_keys(provider, machine, machine_resource)
|
19
|
+
# Create node and client on chef server
|
20
|
+
create_chef_objects(provider, machine, machine_resource, public_key)
|
21
|
+
|
22
|
+
# If the chef server lives on localhost, tunnel the port through to the guest
|
23
|
+
chef_server_url = machine_resource.chef_server[:chef_server_url]
|
24
|
+
url = URI(chef_server_url)
|
25
|
+
# TODO IPv6
|
26
|
+
if is_localhost(url.host)
|
27
|
+
machine.forward_remote_port_to_local(url.port, url.port)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create client.rb and client.pem on machine
|
31
|
+
content = client_rb_content(chef_server_url, machine.node['name'])
|
32
|
+
machine.write_file(provider, client_rb_path, content, :ensure_dir => true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete_chef_objects(provider, node)
|
36
|
+
ChefMetal.inline_resource(provider) do
|
37
|
+
chef_node node['name'] do
|
38
|
+
action :delete
|
39
|
+
end
|
40
|
+
chef_client node['name'] do
|
41
|
+
action :delete
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def create_keys(provider, machine, machine_resource)
|
49
|
+
server_private_key = machine.read_file(client_pem_path)
|
50
|
+
if server_private_key
|
51
|
+
begin
|
52
|
+
server_private_key, format = Cheffish::KeyFormatter.decode(server_private_key)
|
53
|
+
rescue
|
54
|
+
server_private_key = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if server_private_key
|
59
|
+
source_key = source_key_for(machine_resource)
|
60
|
+
if source_key && server_private_key.to_pem != source_key.to_pem
|
61
|
+
# If the server private key does not match our source key, overwrite it
|
62
|
+
server_private_key = source_key
|
63
|
+
if machine_resource.allow_overwrite_keys
|
64
|
+
machine.write_file(provider, client_pem_path, server_private_key.to_pem, :ensure_dir => true)
|
65
|
+
else
|
66
|
+
raise "Private key on machine #{machine_resource.name} does not match desired input key."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
else
|
71
|
+
|
72
|
+
# If the server does not already have keys, create them and upload
|
73
|
+
Cheffish.inline_resource(provider) do
|
74
|
+
private_key 'in_memory' do
|
75
|
+
path :none
|
76
|
+
if machine_resource.private_key_options
|
77
|
+
machine_resource.private_key_options.each_pair do |key,value|
|
78
|
+
send(key, value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
after { |resource, private_key| server_private_key = private_key }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
machine.write_file(provider, client_pem_path, server_private_key.to_pem, :ensure_dir => true)
|
86
|
+
end
|
87
|
+
|
88
|
+
server_private_key.public_key
|
89
|
+
end
|
90
|
+
|
91
|
+
def is_localhost(host)
|
92
|
+
host == '127.0.0.1' || host == 'localhost' || host == '[::1]'
|
93
|
+
end
|
94
|
+
|
95
|
+
def source_key_for(machine_resource)
|
96
|
+
if machine_resource.source_key.is_a?(String)
|
97
|
+
key, format = Cheffish::KeyFormatter.decode(machine_resource.source_key, machine_resource.source_key_pass_phrase)
|
98
|
+
key
|
99
|
+
elsif machine_resource.source_key
|
100
|
+
machine_resource.source_key
|
101
|
+
elsif machine_resource.source_key_path
|
102
|
+
key, format = Cheffish::KeyFormatter.decode(IO.read(machine_resource.source_key_path), machine_resource.source_key_pass_phrase, machine_resource.source_key_path)
|
103
|
+
key
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_chef_objects(provider, machine, machine_resource, public_key)
|
110
|
+
# Save the node and create the client keys and client.
|
111
|
+
ChefMetal.inline_resource(provider) do
|
112
|
+
# Create node
|
113
|
+
# TODO strip automatic attributes first so we don't race with "current state"
|
114
|
+
chef_node machine.node['name'] do
|
115
|
+
chef_server machine_resource.chef_server
|
116
|
+
raw_json machine.node
|
117
|
+
end
|
118
|
+
|
119
|
+
# Create client
|
120
|
+
chef_client machine.node['name'] do
|
121
|
+
chef_server machine_resource.chef_server
|
122
|
+
source_key public_key
|
123
|
+
output_key_path machine_resource.public_key_path
|
124
|
+
output_key_format machine_resource.public_key_format
|
125
|
+
admin machine_resource.admin
|
126
|
+
validator machine_resource.validator
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def client_rb_content(chef_server_url, node_name)
|
132
|
+
<<EOM
|
133
|
+
chef_server_url #{chef_server_url.inspect}
|
134
|
+
node_name #{node_name.inspect}
|
135
|
+
client_key #{client_pem_path.inspect}
|
136
|
+
EOM
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ChefMetal
|
2
|
+
class InlineResource
|
3
|
+
def initialize(provider)
|
4
|
+
@provider = provider
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :provider
|
8
|
+
|
9
|
+
def run_context
|
10
|
+
provider.run_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_symbol, *args, &block)
|
14
|
+
# Stolen ruthlessly from Chef's chef/dsl/recipe.rb
|
15
|
+
|
16
|
+
# Checks the new platform => short_name => resource mapping initially
|
17
|
+
# then fall back to the older approach (Chef::Resource.const_get) for
|
18
|
+
# backward compatibility
|
19
|
+
resource_class = Chef::Resource.resource_for_node(method_symbol, provider.run_context.node)
|
20
|
+
|
21
|
+
super unless resource_class
|
22
|
+
raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0
|
23
|
+
|
24
|
+
# If we have a resource like this one, we want to steal its state
|
25
|
+
args << run_context
|
26
|
+
resource = resource_class.new(*args)
|
27
|
+
resource.source_line = caller[0]
|
28
|
+
resource.load_prior_resource
|
29
|
+
resource.cookbook_name = provider.cookbook_name
|
30
|
+
resource.recipe_name = @recipe_name
|
31
|
+
resource.params = @params
|
32
|
+
# Determine whether this resource is being created in the context of an enclosing Provider
|
33
|
+
resource.enclosing_provider = provider.is_a?(Chef::Provider) ? provider : nil
|
34
|
+
# Evaluate resource attribute DSL
|
35
|
+
resource.instance_eval(&block) if block
|
36
|
+
|
37
|
+
# Run optional resource hook
|
38
|
+
resource.after_created
|
39
|
+
|
40
|
+
# Do NOT put this in the resource collection.
|
41
|
+
#run_context.resource_collection.insert(resource)
|
42
|
+
|
43
|
+
# Instead, run the action directly.
|
44
|
+
Array(resource.action).each do |action|
|
45
|
+
resource.updated_by_last_action(false)
|
46
|
+
run_provider_action(resource.provider_for_action(action))
|
47
|
+
provider.new_resource.updated_by_last_action(true) if resource.updated_by_last_action?
|
48
|
+
end
|
49
|
+
resource
|
50
|
+
end
|
51
|
+
|
52
|
+
# Do Chef::Provider.run_action, but without events
|
53
|
+
def run_provider_action(inline_provider)
|
54
|
+
if !inline_provider.whyrun_supported?
|
55
|
+
raise "#{inline_provider} is not why-run-safe. Only why-run-safe resources are supported in inline_resource."
|
56
|
+
end
|
57
|
+
|
58
|
+
# Blatantly ripped off from chef/provider run_action
|
59
|
+
|
60
|
+
# TODO: it would be preferable to get the action to be executed in the
|
61
|
+
# constructor...
|
62
|
+
|
63
|
+
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
|
64
|
+
inline_provider.load_current_resource
|
65
|
+
inline_provider.define_resource_requirements
|
66
|
+
inline_provider.process_resource_requirements
|
67
|
+
|
68
|
+
# user-defined providers including LWRPs may
|
69
|
+
# not include whyrun support - if they don't support it
|
70
|
+
# we can't execute any actions while we're running in
|
71
|
+
# whyrun mode. Instead we 'fake' whyrun by documenting that
|
72
|
+
# we can't execute the action.
|
73
|
+
# in non-whyrun mode, this will still cause the action to be
|
74
|
+
# executed normally.
|
75
|
+
if inline_provider.whyrun_supported? && !inline_provider.requirements.action_blocked?(@action)
|
76
|
+
inline_provider.send("action_#{inline_provider.action}")
|
77
|
+
elsif !inline_provider.whyrun_mode?
|
78
|
+
inline_provider.send("action_#{inline_provider.action}")
|
79
|
+
end
|
80
|
+
|
81
|
+
if inline_provider.resource_updated?
|
82
|
+
inline_provider.new_resource.updated_by_last_action(true)
|
83
|
+
end
|
84
|
+
|
85
|
+
inline_provider.cleanup_after_converge
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ChefMetal
|
2
|
+
class Machine
|
3
|
+
def initialize(node)
|
4
|
+
@node = node
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :node
|
8
|
+
|
9
|
+
# Sets up everything necessary for convergence to happen on the machine.
|
10
|
+
# The node MUST be saved as part of this procedure. Other than that,
|
11
|
+
# nothing is guaranteed except that converge() will work when this is done.
|
12
|
+
def setup_convergence(provider)
|
13
|
+
raise "setup_convergence not overridden on #{self.class}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def converge(provider)
|
17
|
+
raise "converge not overridden on #{self.class}"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def execute(provider, command)
|
22
|
+
raise "execute not overridden on #{self.class}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_file(path)
|
26
|
+
raise "read_file not overridden on #{self.class}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def download_file(provider, path, local_path)
|
30
|
+
raise "read_file not overridden on #{self.class}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_file(provider, path, content)
|
34
|
+
raise "write_file not overridden on #{self.class}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def upload_file(provider, local_path, path)
|
38
|
+
raise "write_file not overridden on #{self.class}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_dir(provider, path)
|
42
|
+
raise "create_dir not overridden on #{self.class}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Delete file
|
46
|
+
def delete_file(provider, path)
|
47
|
+
raise "delete_file not overridden on #{self.class}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return true or false depending on whether file exists
|
51
|
+
def file_exists?(path)
|
52
|
+
raise "file_exists? not overridden on #{self.class}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return true or false depending on whether remote file differs from local path or content
|
56
|
+
def files_different?(path, local_path, content=nil)
|
57
|
+
raise "file_different? not overridden on #{self.class}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set file attributes { mode, :owner, :group }
|
61
|
+
def set_attributes(provider, path, attributes)
|
62
|
+
raise "set_attributes not overridden on #{self.class}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get file attributes { :mode, :owner, :group }
|
66
|
+
def get_attributes(path)
|
67
|
+
raise "get_attributes not overridden on #{self.class}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Forward a remote port to local for the duration of the current connection
|
71
|
+
def forward_remote_port_to_local(remote_port, local_port)
|
72
|
+
raise "forward_remote_port_to_local not overridden on #{self.class}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def disconnect
|
76
|
+
raise "disconnect not overridden on #{self.class}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'chef_metal/machine'
|
2
|
+
|
3
|
+
module ChefMetal
|
4
|
+
class Machine
|
5
|
+
class BasicMachine < Machine
|
6
|
+
def initialize(node, transport, convergence_strategy)
|
7
|
+
super(node)
|
8
|
+
@transport = transport
|
9
|
+
@convergence_strategy = convergence_strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :transport
|
13
|
+
attr_reader :convergence_strategy
|
14
|
+
|
15
|
+
# Sets up everything necessary for convergence to happen on the machine.
|
16
|
+
# The node MUST be saved as part of this procedure. Other than that,
|
17
|
+
# nothing is guaranteed except that converge() will work when this is done.
|
18
|
+
def setup_convergence(provider, machine_resource)
|
19
|
+
convergence_strategy.setup_convergence(provider, self, machine_resource)
|
20
|
+
end
|
21
|
+
|
22
|
+
def converge(provider)
|
23
|
+
convergence_strategy.converge(provider, self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute(provider, command)
|
27
|
+
provider.converge_by "run '#{command}' on #{node['name']}" do
|
28
|
+
transport.execute(command).error!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute_always(command)
|
33
|
+
transport.execute(command)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_file(path)
|
37
|
+
transport.read_file(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def download_file(provider, path, local_path)
|
41
|
+
if files_different?(path, local_path)
|
42
|
+
provider.converge_by "download file #{path} on #{node['name']} to #{local_path}" do
|
43
|
+
transport.download_file(path, local_path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_file(provider, path, content, options = {})
|
49
|
+
if files_different?(path, nil, content)
|
50
|
+
if options[:ensure_dir]
|
51
|
+
create_dir(provider, dirname_on_machine(path))
|
52
|
+
end
|
53
|
+
provider.converge_by "write file #{path} on #{node['name']}" do
|
54
|
+
transport.write_file(path, content)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def upload_file(provider, local_path, path, options = {})
|
60
|
+
if files_different?(path, local_path)
|
61
|
+
if options[:ensure_dir]
|
62
|
+
create_dir(provider, dirname_on_machine(path))
|
63
|
+
end
|
64
|
+
provider.converge_by "upload file #{local_path} to #{path} on #{node['name']}" do
|
65
|
+
transport.upload_file(local_path, path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def forward_remote_port_to_local(remote_port, local_port)
|
71
|
+
transport.forward_remote_port_to_local(remote_port, local_port)
|
72
|
+
end
|
73
|
+
|
74
|
+
def disconnect
|
75
|
+
transport.disconnect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|