cheffish 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.
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