chef-metal 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +160 -0
  4. data/Rakefile +6 -0
  5. data/lib/chef/provider/fog_key_pair.rb +106 -0
  6. data/lib/chef/provider/machine.rb +60 -0
  7. data/lib/chef/provider/machine_file.rb +39 -0
  8. data/lib/chef/provider/vagrant_box.rb +44 -0
  9. data/lib/chef/provider/vagrant_cluster.rb +39 -0
  10. data/lib/chef/resource/fog_key_pair.rb +34 -0
  11. data/lib/chef/resource/machine.rb +56 -0
  12. data/lib/chef/resource/machine_file.rb +25 -0
  13. data/lib/chef/resource/vagrant_box.rb +18 -0
  14. data/lib/chef/resource/vagrant_cluster.rb +16 -0
  15. data/lib/chef_metal.rb +84 -0
  16. data/lib/chef_metal/aws_credentials.rb +55 -0
  17. data/lib/chef_metal/convergence_strategy.rb +15 -0
  18. data/lib/chef_metal/convergence_strategy/install_msi.rb +41 -0
  19. data/lib/chef_metal/convergence_strategy/install_sh.rb +36 -0
  20. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +140 -0
  21. data/lib/chef_metal/inline_resource.rb +88 -0
  22. data/lib/chef_metal/machine.rb +79 -0
  23. data/lib/chef_metal/machine/basic_machine.rb +79 -0
  24. data/lib/chef_metal/machine/unix_machine.rb +108 -0
  25. data/lib/chef_metal/machine/windows_machine.rb +94 -0
  26. data/lib/chef_metal/provisioner.rb +71 -0
  27. data/lib/chef_metal/provisioner/fog_provisioner.rb +378 -0
  28. data/lib/chef_metal/provisioner/vagrant_provisioner.rb +327 -0
  29. data/lib/chef_metal/recipe_dsl.rb +26 -0
  30. data/lib/chef_metal/transport.rb +36 -0
  31. data/lib/chef_metal/transport/ssh.rb +157 -0
  32. data/lib/chef_metal/transport/winrm.rb +91 -0
  33. data/lib/chef_metal/version.rb +3 -0
  34. 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