cheffish 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +4 -0
  4. data/Rakefile +23 -0
  5. data/lib/chef/provider/chef_client.rb +44 -0
  6. data/lib/chef/provider/chef_data_bag.rb +50 -0
  7. data/lib/chef/provider/chef_data_bag_item.rb +273 -0
  8. data/lib/chef/provider/chef_environment.rb +78 -0
  9. data/lib/chef/provider/chef_node.rb +82 -0
  10. data/lib/chef/provider/chef_role.rb +79 -0
  11. data/lib/chef/provider/chef_user.rb +48 -0
  12. data/lib/chef/provider/private_key.rb +160 -0
  13. data/lib/chef/provider/public_key.rb +83 -0
  14. data/lib/chef/resource/chef_client.rb +44 -0
  15. data/lib/chef/resource/chef_data_bag.rb +18 -0
  16. data/lib/chef/resource/chef_data_bag_item.rb +114 -0
  17. data/lib/chef/resource/chef_environment.rb +71 -0
  18. data/lib/chef/resource/chef_node.rb +18 -0
  19. data/lib/chef/resource/chef_role.rb +104 -0
  20. data/lib/chef/resource/chef_user.rb +51 -0
  21. data/lib/chef/resource/in_parallel.rb +6 -0
  22. data/lib/chef/resource/private_key.rb +39 -0
  23. data/lib/chef/resource/public_key.rb +16 -0
  24. data/lib/cheffish.rb +245 -0
  25. data/lib/cheffish/actor_provider_base.rb +120 -0
  26. data/lib/cheffish/chef_provider_base.rb +222 -0
  27. data/lib/cheffish/cheffish_server_api.rb +21 -0
  28. data/lib/cheffish/inline_resource.rb +88 -0
  29. data/lib/cheffish/key_formatter.rb +93 -0
  30. data/lib/cheffish/recipe_dsl.rb +98 -0
  31. data/lib/cheffish/version.rb +4 -0
  32. data/spec/integration/chef_client_spec.rb +48 -0
  33. data/spec/integration/chef_node_spec.rb +75 -0
  34. data/spec/integration/chef_user_spec.rb +48 -0
  35. data/spec/integration/private_key_spec.rb +356 -0
  36. data/spec/integration/recipe_dsl_spec.rb +29 -0
  37. data/spec/support/key_support.rb +29 -0
  38. data/spec/support/spec_support.rb +148 -0
  39. metadata +124 -0
@@ -0,0 +1,83 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'openssl'
3
+ require 'cheffish/key_formatter'
4
+
5
+ class Chef::Provider::PublicKey < Chef::Provider::LWRPBase
6
+
7
+ action :create do
8
+ if !new_source_key
9
+ raise "No source key specified"
10
+ end
11
+ desired_output = encode_public_key(new_source_key)
12
+ if Array(current_resource.action) == [ :delete ] || desired_output != IO.read(new_resource.path)
13
+ converge_by "write #{new_resource.format} public key #{new_resource.path} from #{new_source_key_publicity} key #{new_resource.source_key_path}" do
14
+ IO.write(new_resource.path, desired_output)
15
+ # TODO permissions on file?
16
+ end
17
+ end
18
+ end
19
+
20
+ action :delete do
21
+ if Array(current_resource.action) == [ :create ]
22
+ converge_by "delete public key #{new_resource.path}" do
23
+ ::File.unlink(new_resource.path)
24
+ end
25
+ end
26
+ end
27
+
28
+ def whyrun_supported?
29
+ true
30
+ end
31
+
32
+ def encode_public_key(key)
33
+ key_format = {}
34
+ key_format[:format] = new_resource.format if new_resource.format
35
+ Cheffish::KeyFormatter.encode(key, key_format)
36
+ end
37
+
38
+ attr_reader :current_public_key
39
+ attr_reader :new_source_key_publicity
40
+
41
+ def new_source_key
42
+ @new_source_key ||= begin
43
+ if new_resource.source_key.is_a?(String)
44
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
45
+ elsif new_resource.source_key
46
+ source_key = new_resource.source_key
47
+ elsif new_resource.source_key_path
48
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
49
+ else
50
+ return nil
51
+ end
52
+
53
+ if source_key.private?
54
+ @new_source_key_publicity = 'private'
55
+ source_key.public_key
56
+ else
57
+ @new_source_key_publicity = 'public'
58
+ source_key
59
+ end
60
+ end
61
+ end
62
+
63
+ def load_current_resource
64
+ if ::File.exist?(new_resource.path)
65
+ resource = Chef::Resource::PublicKey.new(new_resource.path)
66
+ begin
67
+ key, key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.path), nil, new_resource.path)
68
+ if key
69
+ @current_public_key = key
70
+ resource.format key_format[:format]
71
+ end
72
+ rescue
73
+ # If there is an error reading we assume format and such is broken
74
+ end
75
+
76
+ @current_resource = resource
77
+ else
78
+ not_found_resource = Chef::Resource::PublicKey.new(new_resource.path)
79
+ not_found_resource.action :delete
80
+ @current_resource = not_found_resource
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,44 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefClient < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_client'
6
+
7
+ actions :create, :delete, :regenerate_keys, :nothing
8
+ default_action :create
9
+
10
+ def initialize(*args)
11
+ super
12
+ chef_server Cheffish.enclosing_chef_server
13
+ end
14
+
15
+ # Client attributes
16
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
17
+ attribute :admin, :kind_of => [TrueClass, FalseClass]
18
+ attribute :validator, :kind_of => [TrueClass, FalseClass]
19
+
20
+ # Input key
21
+ attribute :source_key # String or OpenSSL::PKey::*
22
+ attribute :source_key_path, :kind_of => String
23
+ attribute :source_key_pass_phrase
24
+
25
+ # Output public key (if so desired)
26
+ attribute :output_key_path, :kind_of => String
27
+ attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
28
+
29
+ # If this is set, client is not patchy
30
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
31
+
32
+ attribute :raw_json, :kind_of => Hash
33
+ attribute :chef_server, :kind_of => Hash
34
+
35
+ # Proc that runs just before the resource executes. Called with (resource)
36
+ def before(&block)
37
+ block ? @before = block : @before
38
+ end
39
+
40
+ # Proc that runs after the resource completes. Called with (resource, json, private_key, public_key)
41
+ def after(&block)
42
+ block ? @after = block : @after
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefDataBag < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_data_bag'
6
+
7
+ actions :create, :delete, :nothing
8
+ default_action :create
9
+
10
+ def initialize(*args)
11
+ super
12
+ chef_server Cheffish.enclosing_chef_server
13
+ end
14
+
15
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
16
+
17
+ attribute :chef_server, :kind_of => Hash
18
+ end
@@ -0,0 +1,114 @@
1
+ require 'cheffish'
2
+ require 'chef/config'
3
+ require 'chef/resource/lwrp_base'
4
+
5
+ class Chef::Resource::ChefDataBagItem < Chef::Resource::LWRPBase
6
+ self.resource_name = 'chef_data_bag_item'
7
+
8
+ actions :create, :delete, :nothing
9
+ default_action :create
10
+
11
+ def initialize(*args)
12
+ super
13
+ name @name
14
+ if !data_bag
15
+ data_bag Cheffish.enclosing_data_bag
16
+ end
17
+ if Cheffish.enclosing_data_bag_item_encryption
18
+ @encrypt = true if Cheffish.enclosing_data_bag_item_encryption[:encrypt_all]
19
+ @secret = Cheffish.enclosing_data_bag_item_encryption[:secret]
20
+ @secret_path = Cheffish.enclosing_data_bag_item_encryption[:secret_path] || Chef::Config[:encrypted_data_bag_secret]
21
+ @encryption_cipher = Cheffish.enclosing_data_bag_item_encryption[:encryption_cipher]
22
+ @encryption_version = Cheffish.enclosing_data_bag_item_encryption[:encryption_version]
23
+ @old_secret = Cheffish.enclosing_data_bag_item_encryption[:old_secret]
24
+ @old_secret_path = Cheffish.enclosing_data_bag_item_encryption[:old_secret_path]
25
+ end
26
+ chef_server Cheffish.enclosing_chef_server
27
+ end
28
+
29
+ def name(*args)
30
+ result = super(*args)
31
+ if args.size == 1
32
+ parts = name.split('/')
33
+ if parts.size == 1
34
+ @id = parts[0]
35
+ elsif parts.size == 2
36
+ @data_bag = parts[0]
37
+ @id = parts[1]
38
+ else
39
+ raise "Name #{args[0].inspect} must be a string with 1 or 2 parts, either 'id' or 'data_bag/id"
40
+ end
41
+ end
42
+ result
43
+ end
44
+
45
+ NOT_PASSED = Object.new
46
+ def id(value = NOT_PASSED)
47
+ if value == NOT_PASSED
48
+ @id
49
+ else
50
+ @id = value
51
+ name data_bag ? "#{data_bag}/#{id}" : id
52
+ end
53
+ end
54
+ def data_bag(value = NOT_PASSED)
55
+ if value == NOT_PASSED
56
+ @data_bag
57
+ else
58
+ @data_bag = value
59
+ name data_bag ? "#{data_bag}/#{id}" : id
60
+ end
61
+ end
62
+ attribute :raw_data, :kind_of => Hash
63
+
64
+ # If secret or secret_path are set, encrypt is assumed true. encrypt exists mainly for with_secret and with_secret_path
65
+ attribute :encrypt, :kind_of => [TrueClass, FalseClass]
66
+ #attribute :secret, :kind_of => String
67
+ def secret(new_secret = nil)
68
+ if !new_secret
69
+ @secret
70
+ else
71
+ @secret = new_secret
72
+ @encrypt = true if @encrypt.nil?
73
+ end
74
+ end
75
+ #attribute :secret_path, :kind_of => String
76
+ def secret_path(new_secret_path = nil)
77
+ if !new_secret_path
78
+ @secret_path
79
+ else
80
+ @secret_path = new_secret_path
81
+ @encrypt = true if @encrypt.nil?
82
+ end
83
+ end
84
+ attribute :encryption_version, :kind_of => Integer, :default => Chef::Config[:data_bag_encrypt_version]
85
+
86
+ # Old secret (or secrets) to read the old data bag when we are changing keys and re-encrypting data
87
+ attribute :old_secret, :kind_of => [String, Array]
88
+ attribute :old_secret_path, :kind_of => [String, Array]
89
+
90
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
91
+ # reset to their defaults)
92
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
93
+
94
+ attribute :raw_json, :kind_of => Hash
95
+ attribute :chef_server, :kind_of => Hash
96
+
97
+ # value 'ip_address', '127.0.0.1'
98
+ # value [ 'pushy', 'port' ], '9000'
99
+ # value 'ip_addresses' do |existing_value|
100
+ # (existing_value || []) + [ '127.0.0.1' ]
101
+ # end
102
+ # value 'ip_address', :delete
103
+ attr_reader :raw_data_modifiers
104
+ def value(raw_data_path, value=NOT_PASSED, &block)
105
+ @raw_data_modifiers ||= []
106
+ if value != NOT_PASSED
107
+ @raw_data_modifiers << [ raw_data_path, value ]
108
+ elsif block
109
+ @raw_data_modifiers << [ raw_data_path, block ]
110
+ else
111
+ raise "value requires either a value or a block"
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,71 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+ require 'chef/environment'
4
+
5
+ class Chef::Resource::ChefEnvironment < Chef::Resource::LWRPBase
6
+ self.resource_name = 'chef_environment'
7
+
8
+ actions :create, :delete, :nothing
9
+ default_action :create
10
+
11
+ def initialize(*args)
12
+ super
13
+ chef_server Cheffish.enclosing_chef_server
14
+ end
15
+
16
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
17
+ attribute :description, :kind_of => String
18
+ attribute :cookbook_versions, :kind_of => Hash, :callbacks => {
19
+ "should have valid cookbook versions" => lambda { |value| Chef::Environment.validate_cookbook_versions(value) }
20
+ }
21
+ attribute :default_attributes, :kind_of => Hash
22
+ attribute :override_attributes, :kind_of => Hash
23
+
24
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
25
+ # reset to their defaults)
26
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
27
+
28
+ attribute :raw_json, :kind_of => Hash
29
+ attribute :chef_server, :kind_of => Hash
30
+
31
+ NOT_PASSED=Object.new
32
+
33
+ # default 'ip_address', '127.0.0.1'
34
+ # default [ 'pushy', 'port' ], '9000'
35
+ # default 'ip_addresses' do |existing_value|
36
+ # (existing_value || []) + [ '127.0.0.1' ]
37
+ # end
38
+ # default 'ip_address', :delete
39
+ attr_reader :default_attribute_modifiers
40
+ def default(attribute_path, value=NOT_PASSED, &block)
41
+ @default_attribute_modifiers ||= []
42
+ if value != NOT_PASSED
43
+ @default_attribute_modifiers << [ attribute_path, value ]
44
+ elsif block
45
+ @default_attribute_modifiers << [ attribute_path, block ]
46
+ else
47
+ raise "default requires either a value or a block"
48
+ end
49
+ end
50
+
51
+ # override 'ip_address', '127.0.0.1'
52
+ # override [ 'pushy', 'port' ], '9000'
53
+ # override 'ip_addresses' do |existing_value|
54
+ # (existing_value || []) + [ '127.0.0.1' ]
55
+ # end
56
+ # override 'ip_address', :delete
57
+ attr_reader :override_attribute_modifiers
58
+ def override(attribute_path, value=NOT_PASSED, &block)
59
+ @override_attribute_modifiers ||= []
60
+ if value != NOT_PASSED
61
+ @override_attribute_modifiers << [ attribute_path, value ]
62
+ elsif block
63
+ @override_attribute_modifiers << [ attribute_path, block ]
64
+ else
65
+ raise "override requires either a value or a block"
66
+ end
67
+ end
68
+
69
+ alias :attributes :default_attributes
70
+ alias :attribute :default
71
+ end
@@ -0,0 +1,18 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefNode < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_node'
6
+
7
+ actions :create, :delete, :nothing
8
+ default_action :create
9
+
10
+ # Grab environment from with_environment
11
+ def initialize(*args)
12
+ super
13
+ chef_environment Cheffish.enclosing_environment
14
+ chef_server Cheffish.enclosing_chef_server
15
+ end
16
+
17
+ Cheffish.node_attributes(self)
18
+ end
@@ -0,0 +1,104 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+ require 'chef/run_list/run_list_item'
4
+
5
+ class Chef::Resource::ChefRole < Chef::Resource::LWRPBase
6
+ self.resource_name = 'chef_role'
7
+
8
+ actions :create, :delete, :nothing
9
+ default_action :create
10
+
11
+ # Grab environment from with_environment
12
+ def initialize(*args)
13
+ super
14
+ chef_server Cheffish.enclosing_chef_server
15
+ end
16
+
17
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
18
+ attribute :description, :kind_of => String
19
+ attribute :run_list, :kind_of => Array # We should let them specify it as a series of parameters too
20
+ attribute :env_run_lists, :kind_of => Hash
21
+ attribute :default_attributes, :kind_of => Hash
22
+ attribute :override_attributes, :kind_of => Hash
23
+
24
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
25
+ # reset to their defaults)
26
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
27
+
28
+ attribute :raw_json, :kind_of => Hash
29
+ attribute :chef_server, :kind_of => Hash
30
+
31
+ NOT_PASSED=Object.new
32
+
33
+ # default_attribute 'ip_address', '127.0.0.1'
34
+ # default_attribute [ 'pushy', 'port' ], '9000'
35
+ # default_attribute 'ip_addresses' do |existing_value|
36
+ # (existing_value || []) + [ '127.0.0.1' ]
37
+ # end
38
+ # default_attribute 'ip_address', :delete
39
+ attr_reader :default_attribute_modifiers
40
+ def default_attribute(attribute_path, value=NOT_PASSED, &block)
41
+ @default_attribute_modifiers ||= []
42
+ if value != NOT_PASSED
43
+ @default_attribute_modifiers << [ attribute_path, value ]
44
+ elsif block
45
+ @default_attribute_modifiers << [ attribute_path, block ]
46
+ else
47
+ raise "default_attribute requires either a value or a block"
48
+ end
49
+ end
50
+
51
+ # override_attribute 'ip_address', '127.0.0.1'
52
+ # override_attribute [ 'pushy', 'port' ], '9000'
53
+ # override_attribute 'ip_addresses' do |existing_value|
54
+ # (existing_value || []) + [ '127.0.0.1' ]
55
+ # end
56
+ # override_attribute 'ip_address', :delete
57
+ attr_reader :override_attribute_modifiers
58
+ def override_attribute(attribute_path, value=NOT_PASSED, &block)
59
+ @override_attribute_modifiers ||= []
60
+ if value != NOT_PASSED
61
+ @override_attribute_modifiers << [ attribute_path, value ]
62
+ elsif block
63
+ @override_attribute_modifiers << [ attribute_path, block ]
64
+ else
65
+ raise "override_attribute requires either a value or a block"
66
+ end
67
+ end
68
+
69
+ # Order matters--if two things here are in the wrong order, they will be flipped in the run list
70
+ # recipe 'apache', 'mysql'
71
+ # recipe 'recipe@version'
72
+ # recipe 'recipe'
73
+ # role ''
74
+ attr_reader :run_list_modifiers
75
+ attr_reader :run_list_removers
76
+ def recipe(*recipes)
77
+ if recipes.size == 0
78
+ raise ArgumentError, "At least one recipe must be specified"
79
+ end
80
+ @run_list_modifiers ||= []
81
+ @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
82
+ end
83
+ def role(*roles)
84
+ if roles.size == 0
85
+ raise ArgumentError, "At least one role must be specified"
86
+ end
87
+ @run_list_modifiers ||= []
88
+ @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
89
+ end
90
+ def remove_recipe(*recipes)
91
+ if recipes.size == 0
92
+ raise ArgumentError, "At least one recipe must be specified"
93
+ end
94
+ @run_list_removers ||= []
95
+ @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
96
+ end
97
+ def remove_role(*roles)
98
+ if roles.size == 0
99
+ raise ArgumentError, "At least one role must be specified"
100
+ end
101
+ @run_list_removers ||= []
102
+ @run_list_removers += roles.map { |recipe| Chef::RunList::RunListItem.new("role[#{role}]") }
103
+ end
104
+ end