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,60 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef/provider/chef_node'
3
+ require 'openssl'
4
+
5
+ class Chef::Provider::Machine < Chef::Provider::LWRPBase
6
+
7
+ use_inline_resources
8
+
9
+ def whyrun_supported?
10
+ true
11
+ end
12
+
13
+ action :create do
14
+ node_json = node_provider.new_json
15
+ node_json['normal']['provisioner_options'] = new_resource.provisioner_options
16
+ machine = new_resource.provisioner.acquire_machine(self, node_json)
17
+ begin
18
+ machine.setup_convergence(self, new_resource)
19
+ # If we were asked to converge, or anything changed, or if a converge has never succeeded, converge.
20
+ if new_resource.converge || (new_resource.converge.nil? && resource_updated?) ||
21
+ !node_json['automatic'] || node_json['automatic'].size == 0
22
+ machine.converge(self)
23
+ end
24
+ ensure
25
+ machine.disconnect
26
+ end
27
+ end
28
+
29
+ action :converge do
30
+ node_json = node_provider.new_json
31
+ node_json['normal']['provisioner_options'] = new_resource.provisioner_options
32
+ machine = new_resource.provisioner.connect_to_machine(node_json)
33
+ begin
34
+ machine.converge(self)
35
+ ensure
36
+ machine.disconnect
37
+ end
38
+ end
39
+
40
+ action :stop do
41
+ node_json = node_provider.new_json
42
+ node_json['normal']['provisioner_options'] = new_resource.provisioner_options
43
+ new_resource.provisioner.stop_machine(self, node_json)
44
+ end
45
+
46
+ action :delete do
47
+ # Grab the node json by asking the provider for it
48
+ node_data = node_provider.current_json
49
+
50
+ # Destroy the machine
51
+ new_resource.provisioner.delete_machine(self, node_data)
52
+ end
53
+
54
+ attr_reader :node_provider
55
+
56
+ def load_current_resource
57
+ @node_provider = Chef::Provider::ChefNode.new(new_resource, nil)
58
+ @node_provider.load_current_resource
59
+ end
60
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'cheffish/cheffish_server_api'
3
+
4
+ class Chef::Provider::MachineFile < Chef::Provider::LWRPBase
5
+
6
+ use_inline_resources
7
+
8
+ def whyrun_supported?
9
+ true
10
+ end
11
+
12
+ def machine
13
+ @machine ||= begin
14
+ if new_resource.machine.kind_of?(ChefMetal::Machine)
15
+ new_resource.machine
16
+ else
17
+ # TODO this is inefficient, can we cache or something?
18
+ node = Cheffish::CheffishServerAPI.new(new_resource.chef_server).get("/nodes/#{new_resource.machine}")
19
+ new_resource.provisioner.connect_to_machine(node)
20
+ end
21
+ end
22
+ end
23
+
24
+ action :upload do
25
+ if new_resource.content
26
+ machine.write_file(self, new_resource.path, new_resource.content)
27
+ else
28
+ machine.upload_file(self, new_resource.local_path, new_resource.path)
29
+ end
30
+ end
31
+
32
+ action :download do
33
+ machine.download_file(provider, new_resource.path, new_resource.local_path)
34
+ end
35
+
36
+ action :delete do
37
+ machine.delete_file(self, new_resource.path)
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef/mixin/shell_out'
3
+
4
+ class Chef::Provider::VagrantBox < Chef::Provider::LWRPBase
5
+
6
+ use_inline_resources
7
+
8
+ include Chef::Mixin::ShellOut
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :create do
15
+ if !list_boxes.has_key?(new_resource.name)
16
+ if new_resource.url
17
+ converge_by "run 'vagrant box add #{new_resource.name} #{new_resource.url}'" do
18
+ shell_out("vagrant box add #{new_resource.name} #{new_resource.url}").error!
19
+ end
20
+ else
21
+ raise "Box #{new_resource.name} does not exist"
22
+ end
23
+ end
24
+ end
25
+
26
+ action :delete do
27
+ if list_boxes.has_key?(new_resource.name)
28
+ converge_by "run 'vagrant box remove #{new_resource.name} #{list_boxes[new_resource.name]}'" do
29
+ shell_out("vagrant box remove #{new_resource.name} #{list_boxes[new_resource.name]}").error!
30
+ end
31
+ end
32
+ end
33
+
34
+ def list_boxes
35
+ @list_boxes ||= shell_out("vagrant box list").stdout.lines.inject({}) do |result, line|
36
+ line =~ /^(\S+)\s+\((.+)\)\s*$/
37
+ result[$1] = $2
38
+ result
39
+ end
40
+ end
41
+
42
+ def load_current_resource
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/provider/lwrp_base'
2
+
3
+ class Chef::Provider::VagrantCluster < Chef::Provider::LWRPBase
4
+
5
+ use_inline_resources
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ action :create do
12
+ the_base_path = new_resource.path
13
+ ChefMetal.inline_resource(self) do
14
+ directory the_base_path
15
+ file ::File.join(the_base_path, 'Vagrantfile') do
16
+ content <<EOM
17
+ Dir.glob('#{::File.join(the_base_path, '*.vm')}') do |vm_file|
18
+ eval(IO.read(vm_file), nil, vm_file)
19
+ end
20
+ EOM
21
+ end
22
+ end
23
+ end
24
+
25
+ action :delete do
26
+ the_base_path = new_resource.path
27
+ ChefMetal.inline_resource(self) do
28
+ file ::File.join(the_base_path, 'Vagrantfile') do
29
+ action :delete
30
+ end
31
+ directory the_base_path do
32
+ action :delete
33
+ end
34
+ end
35
+ end
36
+
37
+ def load_current_resource
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ require 'chef_metal'
2
+
3
+ class Chef::Resource::FogKeyPair < Chef::Resource::LWRPBase
4
+ self.resource_name = 'fog_key_pair'
5
+
6
+ def initialize(*args)
7
+ super
8
+ @provisioner = ChefMetal.enclosing_provisioner
9
+ end
10
+
11
+ def after_created
12
+ # Make the credentials usable
13
+ provisioner.key_pairs[name] = self
14
+ end
15
+
16
+ actions :create, :delete, :nothing
17
+ default_action :create
18
+
19
+ attribute :provisioner
20
+ # Private key to use as input (will be generated if it does not exist)
21
+ attribute :private_key_path, :kind_of => String
22
+ # Public key to use as input (will be generated if it does not exist)
23
+ attribute :public_key_path, :kind_of => String
24
+ # List of parameters to the private_key resource used for generation of the key
25
+ attribute :private_key_options, :kind_of => Hash
26
+
27
+ # TODO what is the right default for this?
28
+ attribute :allow_overwrite, :kind_of => [TrueClass, FalseClass], :default => false
29
+
30
+ # Proc that runs after the resource completes. Called with (resource, private_key, public_key)
31
+ def after(&block)
32
+ block ? @after = block : @after
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'cheffish'
3
+ require 'chef_metal'
4
+
5
+ class Chef::Resource::Machine < Chef::Resource::LWRPBase
6
+ self.resource_name = 'machine'
7
+
8
+ def initialize(*args)
9
+ super
10
+ @chef_environment = Cheffish.enclosing_environment
11
+ @chef_server = Cheffish.enclosing_chef_server
12
+ @provisioner = ChefMetal.enclosing_provisioner
13
+ @provisioner_options = ChefMetal.enclosing_provisioner_options
14
+ end
15
+
16
+ def after_created
17
+ # Notify the provisioner of this machine's creation
18
+ @provisioner.resource_created(self)
19
+ end
20
+
21
+ actions :create, :delete, :stop, :converge, :nothing
22
+ default_action :create
23
+
24
+ # Provisioner attributes
25
+ attribute :provisioner
26
+ attribute :provisioner_options
27
+
28
+ # Node attributes
29
+ Cheffish.node_attributes(self)
30
+
31
+ # Client keys
32
+ # Options to generate private key (size, type, etc.) when the server doesn't have it
33
+ attribute :private_key_options, :kind_of => String
34
+
35
+ # Optionally pull the public key out to a file
36
+ attribute :public_key_path, :kind_of => String
37
+ attribute :public_key_format, :kind_of => String
38
+
39
+ # If you really want to force the private key to be a certain key, pass these
40
+ attribute :source_key
41
+ attribute :source_key_path, :kind_of => String
42
+ attribute :source_key_pass_phrase
43
+
44
+ # Client attributes
45
+ attribute :admin, :kind_of => [TrueClass, FalseClass]
46
+ attribute :validator, :kind_of => [TrueClass, FalseClass]
47
+
48
+ # Allows you to turn convergence off in the :create action by writing "converge false"
49
+ # or force it with "true"
50
+ attribute :converge, :kind_of => [TrueClass, FalseClass]
51
+
52
+ # chef client version and omnibus
53
+ # chef-zero boot method?
54
+ # chef-client -z boot method?
55
+ # pushy boot method?
56
+ end
@@ -0,0 +1,25 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'chef_metal'
3
+ require 'chef_metal/machine'
4
+ require 'chef_metal/provisioner'
5
+
6
+ class Chef::Resource::MachineFile < Chef::Resource::LWRPBase
7
+ self.resource_name = 'machine_file'
8
+
9
+ def initialize(*args)
10
+ super
11
+ @chef_server = Cheffish.enclosing_chef_server
12
+ @provisioner = ChefMetal.enclosing_provisioner
13
+ end
14
+
15
+ actions :upload, :download, :delete, :nothing
16
+ default_action :upload
17
+
18
+ attribute :path, :kind_of => String, :name_attribute => true
19
+ attribute :machine, :kind_of => [String, ChefMetal::Machine]
20
+ attribute :local_path, :kind_of => String
21
+ attribute :content
22
+
23
+ attribute :chef_server, :kind_of => Hash
24
+ attribute :provisioner, :kind_of => ChefMetal::Provisioner
25
+ end
@@ -0,0 +1,18 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'chef_metal/provisioner/vagrant_provisioner'
3
+
4
+ class Chef::Resource::VagrantBox < Chef::Resource::LWRPBase
5
+ self.resource_name = 'vagrant_box'
6
+
7
+ actions :create, :delete, :nothing
8
+ default_action :create
9
+
10
+ attribute :name, :kind_of => String, :name_attribute => true
11
+ attribute :url, :kind_of => String
12
+ attribute :provisioner_options, :kind_of => Hash
13
+
14
+ def after_created
15
+ super
16
+ ChefMetal.with_vagrant_box self
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'chef/resource/lwrp_base'
2
+ require 'chef_metal'
3
+
4
+ class Chef::Resource::VagrantCluster < Chef::Resource::LWRPBase
5
+ self.resource_name = 'vagrant_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
+ ChefMetal.with_vagrant_cluster path
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ # Include recipe basics so require 'chef_metal' will load everything
2
+ require 'chef_metal/recipe_dsl'
3
+ require 'chef/resource/machine'
4
+ require 'chef/provider/machine'
5
+ require 'chef/resource/machine_file'
6
+ require 'chef/provider/machine_file'
7
+ require 'chef/resource/vagrant_cluster'
8
+ require 'chef/provider/vagrant_cluster'
9
+ require 'chef/resource/vagrant_box'
10
+ require 'chef/provider/vagrant_box'
11
+ require 'chef/resource/fog_key_pair'
12
+ require 'chef/provider/fog_key_pair'
13
+ require 'chef_metal/provisioner/fog_provisioner'
14
+
15
+ require 'chef_metal/inline_resource'
16
+
17
+ module ChefMetal
18
+ def self.with_provisioner(provisioner)
19
+ old_provisioner = ChefMetal.enclosing_provisioner
20
+ ChefMetal.enclosing_provisioner = provisioner
21
+ if block_given?
22
+ begin
23
+ yield
24
+ ensure
25
+ ChefMetal.enclosing_provisioner = old_provisioner
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.with_provisioner_options(provisioner_options)
31
+ old_provisioner_options = ChefMetal.enclosing_provisioner_options
32
+ ChefMetal.enclosing_provisioner_options = provisioner_options
33
+ if block_given?
34
+ begin
35
+ yield
36
+ ensure
37
+ ChefMetal.enclosing_provisioner_options = old_provisioner_options
38
+ end
39
+ end
40
+ end
41
+
42
+ def self.with_vagrant_cluster(cluster_path, &block)
43
+ require 'chef_metal/provisioner/vagrant_provisioner'
44
+
45
+ with_provisioner(ChefMetal::Provisioner::VagrantProvisioner.new(cluster_path), &block)
46
+ end
47
+
48
+ def self.with_vagrant_box(box_name, provisioner_options = nil, &block)
49
+ require 'chef/resource/vagrant_box'
50
+
51
+ if box_name.is_a?(Chef::Resource::VagrantBox)
52
+ provisioner_options ||= box_name.provisioner_options || {}
53
+ provisioner_options['vagrant_options'] ||= {}
54
+ provisioner_options['vagrant_options']['vm.box'] = box_name.name
55
+ provisioner_options['vagrant_options']['vm.box_url'] = box_name.url if box_name.url
56
+ else
57
+ provisioner_options ||= {}
58
+ provisioner_options['vagrant_options'] ||= {}
59
+ provisioner_options['vagrant_options']['vm.box'] = box_name
60
+ end
61
+
62
+ with_provisioner_options(provisioner_options, &block)
63
+ end
64
+
65
+ def self.inline_resource(provider, &block)
66
+ InlineResource.new(provider).instance_eval(&block)
67
+ end
68
+
69
+ @@enclosing_provisioner = nil
70
+ def self.enclosing_provisioner
71
+ @@enclosing_provisioner
72
+ end
73
+ def self.enclosing_provisioner=(provisioner)
74
+ @@enclosing_provisioner = provisioner
75
+ end
76
+
77
+ @@enclosing_provisioner_options = nil
78
+ def self.enclosing_provisioner_options
79
+ @@enclosing_provisioner_options
80
+ end
81
+ def self.enclosing_provisioner_options=(provisioner_options)
82
+ @@enclosing_provisioner_options = provisioner_options
83
+ end
84
+ end
@@ -0,0 +1,55 @@
1
+ module ChefMetal
2
+ # Reads in a credentials file in Amazon's download format and presents the credentials to you
3
+ class AWSCredentials
4
+ def initialize
5
+ @credentials = {}
6
+ end
7
+
8
+ def default
9
+ @credentials['default'] || @credentials.first[1]
10
+ end
11
+
12
+ def keys
13
+ @credentials.keys
14
+ end
15
+
16
+ def [](name)
17
+ @credentials[name]
18
+ end
19
+
20
+ def load_ini(credentials_ini_file)
21
+ require 'inifile'
22
+ inifile = IniFile.load(File.expand_path(credentials_ini_file))
23
+ inifile.each_section do |section|
24
+ @credentials[section] = {
25
+ :access_key_id => inifile[section]['aws_access_key_id'],
26
+ :secret_access_key => inifile[section]['aws_secret_access_key'],
27
+ :region => inifile[section]['region']
28
+ }
29
+ end
30
+ end
31
+
32
+ def load_csv(credentials_csv_file)
33
+ require 'csv'
34
+ CSV.new(File.open(credentials_csv_file), :headers => :first_row).each do |row|
35
+ @credentials[row['User Name']] = {
36
+ :user_name => row['User Name'],
37
+ :access_key_id => row['Access Key Id'],
38
+ :secret_access_key => row['Secret Access Key']
39
+ }
40
+ end
41
+ end
42
+
43
+ def load_default
44
+ load_ini('~/.aws/config')
45
+ end
46
+
47
+ def self.method_missing(name, *args, &block)
48
+ singleton.send(name, *args, &block)
49
+ end
50
+
51
+ def self.singleton
52
+ @aws_credentials ||= AWSCredentials.new
53
+ end
54
+ end
55
+ end