cheffish 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -201
  3. data/README.md +120 -117
  4. data/Rakefile +23 -23
  5. data/lib/chef/provider/chef_acl.rb +439 -434
  6. data/lib/chef/provider/chef_client.rb +53 -48
  7. data/lib/chef/provider/chef_container.rb +55 -50
  8. data/lib/chef/provider/chef_data_bag.rb +55 -50
  9. data/lib/chef/provider/chef_data_bag_item.rb +278 -273
  10. data/lib/chef/provider/chef_environment.rb +83 -78
  11. data/lib/chef/provider/chef_group.rb +83 -78
  12. data/lib/chef/provider/chef_mirror.rb +169 -164
  13. data/lib/chef/provider/chef_node.rb +87 -82
  14. data/lib/chef/provider/chef_organization.rb +155 -150
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +46 -41
  16. data/lib/chef/provider/chef_role.rb +84 -79
  17. data/lib/chef/provider/chef_user.rb +59 -54
  18. data/lib/chef/provider/private_key.rb +225 -220
  19. data/lib/chef/provider/public_key.rb +88 -82
  20. data/lib/chef/resource/chef_acl.rb +69 -65
  21. data/lib/chef/resource/chef_client.rb +48 -44
  22. data/lib/chef/resource/chef_container.rb +22 -18
  23. data/lib/chef/resource/chef_data_bag.rb +22 -18
  24. data/lib/chef/resource/chef_data_bag_item.rb +121 -114
  25. data/lib/chef/resource/chef_environment.rb +77 -71
  26. data/lib/chef/resource/chef_group.rb +53 -49
  27. data/lib/chef/resource/chef_mirror.rb +52 -48
  28. data/lib/chef/resource/chef_node.rb +22 -18
  29. data/lib/chef/resource/chef_organization.rb +69 -64
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +35 -31
  31. data/lib/chef/resource/chef_role.rb +110 -104
  32. data/lib/chef/resource/chef_user.rb +56 -52
  33. data/lib/chef/resource/private_key.rb +48 -44
  34. data/lib/chef/resource/public_key.rb +25 -21
  35. data/lib/cheffish.rb +235 -233
  36. data/lib/cheffish/actor_provider_base.rb +131 -131
  37. data/lib/cheffish/basic_chef_client.rb +184 -184
  38. data/lib/cheffish/chef_provider_base.rb +246 -246
  39. data/lib/cheffish/chef_run.rb +162 -155
  40. data/lib/cheffish/chef_run_data.rb +19 -19
  41. data/lib/cheffish/chef_run_listener.rb +30 -30
  42. data/lib/cheffish/key_formatter.rb +113 -113
  43. data/lib/cheffish/merged_config.rb +94 -94
  44. data/lib/cheffish/recipe_dsl.rb +157 -157
  45. data/lib/cheffish/rspec.rb +8 -8
  46. data/lib/cheffish/rspec/chef_run_support.rb +83 -83
  47. data/lib/cheffish/rspec/matchers.rb +4 -4
  48. data/lib/cheffish/rspec/matchers/be_idempotent.rb +16 -16
  49. data/lib/cheffish/rspec/matchers/emit_no_warnings_or_errors.rb +15 -15
  50. data/lib/cheffish/rspec/matchers/have_updated.rb +37 -37
  51. data/lib/cheffish/rspec/matchers/partially_match.rb +63 -63
  52. data/lib/cheffish/rspec/recipe_run_wrapper.rb +59 -47
  53. data/lib/cheffish/rspec/repository_support.rb +108 -108
  54. data/lib/cheffish/server_api.rb +52 -52
  55. data/lib/cheffish/version.rb +3 -3
  56. data/lib/cheffish/with_pattern.rb +21 -21
  57. data/spec/functional/fingerprint_spec.rb +64 -64
  58. data/spec/functional/merged_config_spec.rb +19 -19
  59. data/spec/functional/server_api_spec.rb +13 -13
  60. data/spec/integration/chef_acl_spec.rb +879 -879
  61. data/spec/integration/chef_client_spec.rb +105 -105
  62. data/spec/integration/chef_container_spec.rb +33 -33
  63. data/spec/integration/chef_group_spec.rb +309 -309
  64. data/spec/integration/chef_mirror_spec.rb +491 -491
  65. data/spec/integration/chef_node_spec.rb +786 -786
  66. data/spec/integration/chef_organization_spec.rb +226 -226
  67. data/spec/integration/chef_role_spec.rb +78 -0
  68. data/spec/integration/chef_user_spec.rb +85 -85
  69. data/spec/integration/private_key_spec.rb +399 -399
  70. data/spec/integration/recipe_dsl_spec.rb +28 -28
  71. data/spec/integration/rspec/converge_spec.rb +183 -183
  72. data/spec/support/key_support.rb +29 -29
  73. data/spec/support/spec_support.rb +15 -15
  74. data/spec/unit/get_private_key_spec.rb +131 -131
  75. data/spec/unit/recipe_run_wrapper_spec.rb +37 -0
  76. metadata +8 -5
@@ -1,52 +1,56 @@
1
- require 'cheffish'
2
- require 'chef/resource/lwrp_base'
3
-
4
- class Chef::Resource::ChefUser < Chef::Resource::LWRPBase
5
- self.resource_name = 'chef_user'
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_server run_context.cheffish.current_chef_server
14
- end
15
-
16
- # Client attributes
17
- attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
18
- attribute :display_name, :kind_of => String
19
- attribute :admin, :kind_of => [TrueClass, FalseClass]
20
- attribute :email, :kind_of => String
21
- attribute :external_authentication_uid
22
- attribute :recovery_authentication_enabled, :kind_of => [TrueClass, FalseClass]
23
- attribute :password, :kind_of => String # Hmm. There is no way to idempotentize this.
24
- #attribute :salt # TODO server doesn't support sending or receiving these, but it's the only way to backup / restore a user
25
- #attribute :hashed_password
26
- #attribute :hash_type
27
-
28
- # Input key
29
- attribute :source_key # String or OpenSSL::PKey::*
30
- attribute :source_key_path, :kind_of => String
31
- attribute :source_key_pass_phrase
32
-
33
- # Output public key (if so desired)
34
- attribute :output_key_path, :kind_of => String
35
- attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
36
-
37
- # If this is set, client is not patchy
38
- attribute :complete, :kind_of => [TrueClass, FalseClass]
39
-
40
- attribute :raw_json, :kind_of => Hash
41
- attribute :chef_server, :kind_of => Hash
42
-
43
- # Proc that runs just before the resource executes. Called with (resource)
44
- def before(&block)
45
- block ? @before = block : @before
46
- end
47
-
48
- # Proc that runs after the resource completes. Called with (resource, json, private_key, public_key)
49
- def after(&block)
50
- block ? @after = block : @after
51
- end
52
- end
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef
5
+ class Resource
6
+ class ChefUser < Chef::Resource::LWRPBase
7
+ self.resource_name = 'chef_user'
8
+
9
+ actions :create, :delete, :nothing
10
+ default_action :create
11
+
12
+ # Grab environment from with_environment
13
+ def initialize(*args)
14
+ super
15
+ chef_server run_context.cheffish.current_chef_server
16
+ end
17
+
18
+ # Client attributes
19
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
20
+ attribute :display_name, :kind_of => String
21
+ attribute :admin, :kind_of => [TrueClass, FalseClass]
22
+ attribute :email, :kind_of => String
23
+ attribute :external_authentication_uid
24
+ attribute :recovery_authentication_enabled, :kind_of => [TrueClass, FalseClass]
25
+ attribute :password, :kind_of => String # Hmm. There is no way to idempotentize this.
26
+ #attribute :salt # TODO server doesn't support sending or receiving these, but it's the only way to backup / restore a user
27
+ #attribute :hashed_password
28
+ #attribute :hash_type
29
+
30
+ # Input key
31
+ attribute :source_key # String or OpenSSL::PKey::*
32
+ attribute :source_key_path, :kind_of => String
33
+ attribute :source_key_pass_phrase
34
+
35
+ # Output public key (if so desired)
36
+ attribute :output_key_path, :kind_of => String
37
+ attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
38
+
39
+ # If this is set, client is not patchy
40
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
41
+
42
+ attribute :raw_json, :kind_of => Hash
43
+ attribute :chef_server, :kind_of => Hash
44
+
45
+ # Proc that runs just before the resource executes. Called with (resource)
46
+ def before(&block)
47
+ block ? @before = block : @before
48
+ end
49
+
50
+ # Proc that runs after the resource completes. Called with (resource, json, private_key, public_key)
51
+ def after(&block)
52
+ block ? @after = block : @after
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,44 +1,48 @@
1
- require 'openssl/cipher'
2
- require 'chef/resource/lwrp_base'
3
-
4
- class Chef::Resource::PrivateKey < Chef::Resource::LWRPBase
5
- self.resource_name = 'private_key'
6
-
7
- actions :create, :delete, :regenerate, :nothing
8
- default_action :create
9
-
10
- # Path to private key. Set to :none to create the key in memory and not on disk.
11
- attribute :path, :kind_of => [ String, Symbol ], :name_attribute => true
12
- attribute :format, :kind_of => Symbol, :default => :pem, :equal_to => [ :pem, :der ]
13
- attribute :type, :kind_of => Symbol, :default => :rsa, :equal_to => [ :rsa, :dsa ] # TODO support :ec
14
- # These specify an optional public_key you can spit out if you want.
15
- attribute :public_key_path, :kind_of => String
16
- attribute :public_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :openssh, :pem, :der ]
17
- # Specify this if you want to copy another private key but give it a different format / password
18
- attribute :source_key
19
- attribute :source_key_path, :kind_of => String
20
- attribute :source_key_pass_phrase
21
-
22
- # RSA and DSA
23
- attribute :size, :kind_of => Integer, :default => 2048
24
-
25
- # RSA-only
26
- attribute :exponent, :kind_of => Integer # For RSA
27
-
28
- # PEM-only
29
- attribute :pass_phrase, :kind_of => String
30
- attribute :cipher, :kind_of => String, :default => 'DES-EDE3-CBC', :equal_to => OpenSSL::Cipher.ciphers
31
-
32
- # Set this to regenerate the key if it does not have the desired characteristics (like size, type, etc.)
33
- attribute :regenerate_if_different, :kind_of => [TrueClass, FalseClass]
34
-
35
- # Proc that runs after the resource completes. Called with (resource, private_key)
36
- def after(&block)
37
- block ? @after = block : @after
38
- end
39
-
40
- # We are not interested in Chef's cloning behavior here.
41
- def load_prior_resource(*args)
42
- Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
43
- end
44
- end
1
+ require 'openssl/cipher'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef
5
+ class Resource
6
+ class PrivateKey < Chef::Resource::LWRPBase
7
+ self.resource_name = 'private_key'
8
+
9
+ actions :create, :delete, :regenerate, :nothing
10
+ default_action :create
11
+
12
+ # Path to private key. Set to :none to create the key in memory and not on disk.
13
+ attribute :path, :kind_of => [ String, Symbol ], :name_attribute => true
14
+ attribute :format, :kind_of => Symbol, :default => :pem, :equal_to => [ :pem, :der ]
15
+ attribute :type, :kind_of => Symbol, :default => :rsa, :equal_to => [ :rsa, :dsa ] # TODO support :ec
16
+ # These specify an optional public_key you can spit out if you want.
17
+ attribute :public_key_path, :kind_of => String
18
+ attribute :public_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :openssh, :pem, :der ]
19
+ # Specify this if you want to copy another private key but give it a different format / password
20
+ attribute :source_key
21
+ attribute :source_key_path, :kind_of => String
22
+ attribute :source_key_pass_phrase
23
+
24
+ # RSA and DSA
25
+ attribute :size, :kind_of => Integer, :default => 2048
26
+
27
+ # RSA-only
28
+ attribute :exponent, :kind_of => Integer # For RSA
29
+
30
+ # PEM-only
31
+ attribute :pass_phrase, :kind_of => String
32
+ attribute :cipher, :kind_of => String, :default => 'DES-EDE3-CBC', :equal_to => OpenSSL::Cipher.ciphers
33
+
34
+ # Set this to regenerate the key if it does not have the desired characteristics (like size, type, etc.)
35
+ attribute :regenerate_if_different, :kind_of => [TrueClass, FalseClass]
36
+
37
+ # Proc that runs after the resource completes. Called with (resource, private_key)
38
+ def after(&block)
39
+ block ? @after = block : @after
40
+ end
41
+
42
+ # We are not interested in Chef's cloning behavior here.
43
+ def load_prior_resource(*args)
44
+ Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,21 +1,25 @@
1
- require 'openssl/cipher'
2
- require 'chef/resource/lwrp_base'
3
-
4
- class Chef::Resource::PublicKey < Chef::Resource::LWRPBase
5
- self.resource_name = 'public_key'
6
-
7
- actions :create, :delete, :nothing
8
- default_action :create
9
-
10
- attribute :path, :kind_of => String, :name_attribute => true
11
- attribute :format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
12
-
13
- attribute :source_key
14
- attribute :source_key_path, :kind_of => String
15
- attribute :source_key_pass_phrase
16
-
17
- # We are not interested in Chef's cloning behavior here.
18
- def load_prior_resource(*args)
19
- Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
20
- end
21
- end
1
+ require 'openssl/cipher'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef
5
+ class Resource
6
+ class PublicKey < Chef::Resource::LWRPBase
7
+ self.resource_name = 'public_key'
8
+
9
+ actions :create, :delete, :nothing
10
+ default_action :create
11
+
12
+ attribute :path, :kind_of => String, :name_attribute => true
13
+ attribute :format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
14
+
15
+ attribute :source_key
16
+ attribute :source_key_path, :kind_of => String
17
+ attribute :source_key_pass_phrase
18
+
19
+ # We are not interested in Chef's cloning behavior here.
20
+ def load_prior_resource(*args)
21
+ Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,233 +1,235 @@
1
- module Cheffish
2
- NAME_REGEX = /^[.\-[:alnum:]_]+$/
3
-
4
- def self.inline_resource(provider, provider_action, *resources, &block)
5
- BasicChefClient.inline_resource(provider, provider_action, *resources, &block)
6
- end
7
-
8
- def self.default_chef_server(config = profiled_config)
9
- {
10
- :chef_server_url => config[:chef_server_url],
11
- :options => {
12
- :client_name => config[:node_name],
13
- :signing_key_filename => config[:client_key]
14
- }
15
- }
16
- end
17
-
18
- def self.chef_server_api(chef_server = default_chef_server)
19
- # Pin the server api version to 0 until https://github.com/chef/cheffish/issues/56
20
- # gets the correct compatibility fix.
21
- chef_server[:options] ||= {}
22
- chef_server[:options].merge!(api_version: "0")
23
- Cheffish::ServerAPI.new(chef_server[:chef_server_url], chef_server[:options])
24
- end
25
-
26
- def self.profiled_config(config = Chef::Config)
27
- if config.profile && config.profiles && config.profiles[config.profile]
28
- MergedConfig.new(config.profiles[config.profile], config)
29
- else
30
- config
31
- end
32
- end
33
-
34
- def self.load_chef_config(chef_config = Chef::Config)
35
- if ::Gem::Version.new(::Chef::VERSION) >= ::Gem::Version.new('12.0.0')
36
- chef_config.config_file = ::Chef::Knife.chef_config_dir
37
- else
38
- chef_config.config_file = ::Chef::Knife.locate_config_file
39
- end
40
- config_fetcher = Chef::ConfigFetcher.new(chef_config.config_file, chef_config.config_file_jail)
41
- if chef_config.config_file.nil?
42
- Chef::Log.warn("No config file found or specified on command line, using command line options.")
43
- elsif config_fetcher.config_missing?
44
- Chef::Log.warn("Did not find config file: #{chef_config.config_file}, using command line options.")
45
- else
46
- config_content = config_fetcher.read_config
47
- config_file_path = chef_config.config_file
48
- begin
49
- chef_config.from_string(config_content, config_file_path)
50
- rescue Exception => error
51
- Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
52
- filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
53
- filtered_trace.each {|line| Chef::Log.fatal(" " + line )}
54
- Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
55
- end
56
- end
57
- Cheffish.profiled_config(chef_config)
58
- end
59
-
60
- def self.honor_local_mode(local_mode_default = true, &block)
61
- if !Chef::Config.has_key?(:local_mode) && !local_mode_default.nil?
62
- Chef::Config.local_mode = local_mode_default
63
- end
64
- if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
65
- Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
66
- end
67
- begin
68
- require 'chef/local_mode'
69
- Chef::LocalMode.with_server_connectivity(&block)
70
-
71
- rescue LoadError
72
- Chef::Application.setup_server_connectivity
73
- if block_given?
74
- begin
75
- yield
76
- ensure
77
- Chef::Application.destroy_server_connectivity
78
- end
79
- end
80
- end
81
- end
82
-
83
- def self.get_private_key(name, config = profiled_config)
84
- key, key_path = get_private_key_with_path(name, config)
85
- key
86
- end
87
-
88
- def self.get_private_key_with_path(name, config = profiled_config)
89
- if config[:private_keys] && config[:private_keys][name]
90
- named_key = config[:private_keys][name]
91
- if named_key.is_a?(String)
92
- Chef::Log.info("Got key #{name} from Chef::Config.private_keys.#{name}, which points at #{named_key}. Reading key from there ...")
93
- return [ IO.read(named_key), named_key]
94
- else
95
- Chef::Log.info("Got key #{name} raw from Chef::Config.private_keys.#{name}.")
96
- return [ named_key.to_pem, nil ]
97
- end
98
- elsif config[:private_key_paths]
99
- config[:private_key_paths].each do |private_key_path|
100
- next unless File.exist?(private_key_path)
101
- Dir.entries(private_key_path).sort.each do |key|
102
- ext = File.extname(key)
103
- if ext == '' || ext == '.pem'
104
- key_name = key[0..-(ext.length+1)]
105
- if key_name == name
106
- Chef::Log.info("Reading key #{name} from file #{private_key_path}/#{key}")
107
- return [ IO.read("#{private_key_path}/#{key}"), "#{private_key_path}/#{key}" ]
108
- end
109
- end
110
- end
111
- end
112
- end
113
- nil
114
- end
115
-
116
- NOT_PASSED=Object.new
117
-
118
- def self.node_attributes(klass)
119
- klass.class_eval do
120
- attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
121
- attribute :chef_environment, :kind_of => String, :regex => Cheffish::NAME_REGEX
122
- attribute :run_list, :kind_of => Array # We should let them specify it as a series of parameters too
123
- attribute :attributes, :kind_of => Hash
124
-
125
- # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
126
- # reset to their defaults)
127
- attribute :complete, :kind_of => [TrueClass, FalseClass]
128
-
129
- attribute :raw_json, :kind_of => Hash
130
- attribute :chef_server, :kind_of => Hash
131
-
132
- # attribute 'ip_address', '127.0.0.1'
133
- # attribute [ 'pushy', 'port' ], '9000'
134
- # attribute 'ip_addresses' do |existing_value|
135
- # (existing_value || []) + [ '127.0.0.1' ]
136
- # end
137
- # attribute 'ip_address', :delete
138
- attr_accessor :attribute_modifiers
139
- def attribute(attribute_path, value=NOT_PASSED, &block)
140
- @attribute_modifiers ||= []
141
- if value != NOT_PASSED
142
- @attribute_modifiers << [ attribute_path, value ]
143
- elsif block
144
- @attribute_modifiers << [ attribute_path, block ]
145
- else
146
- raise "attribute requires either a value or a block"
147
- end
148
- end
149
-
150
- # Patchy tags
151
- # tag 'webserver', 'apache', 'myenvironment'
152
- def tag(*tags)
153
- attribute 'tags' do |existing_tags|
154
- existing_tags ||= []
155
- tags.each do |tag|
156
- if !existing_tags.include?(tag.to_s)
157
- existing_tags << tag.to_s
158
- end
159
- end
160
- existing_tags
161
- end
162
- end
163
- def remove_tag(*tags)
164
- attribute 'tags' do |existing_tags|
165
- if existing_tags
166
- tags.each do |tag|
167
- existing_tags.delete(tag.to_s)
168
- end
169
- end
170
- existing_tags
171
- end
172
- end
173
-
174
- # NON-patchy tags
175
- # tags :a, :b, :c # removes all other tags
176
- def tags(*tags)
177
- if tags.size == 0
178
- attribute('tags')
179
- else
180
- tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array)
181
- attribute 'tags', tags.map { |tag| tag.to_s }
182
- end
183
- end
184
-
185
- # Order matters--if two things here are in the wrong order, they will be flipped in the run list
186
- # recipe 'apache', 'mysql'
187
- # recipe 'recipe@version'
188
- # recipe 'recipe'
189
- # role ''
190
- attr_accessor :run_list_modifiers
191
- attr_accessor :run_list_removers
192
- def recipe(*recipes)
193
- if recipes.size == 0
194
- raise ArgumentError, "At least one recipe must be specified"
195
- end
196
- @run_list_modifiers ||= []
197
- @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
198
- end
199
- def role(*roles)
200
- if roles.size == 0
201
- raise ArgumentError, "At least one role must be specified"
202
- end
203
- @run_list_modifiers ||= []
204
- @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
205
- end
206
- def remove_recipe(*recipes)
207
- if recipes.size == 0
208
- raise ArgumentError, "At least one recipe must be specified"
209
- end
210
- @run_list_removers ||= []
211
- @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
212
- end
213
- def remove_role(*roles)
214
- if roles.size == 0
215
- raise ArgumentError, "At least one role must be specified"
216
- end
217
- @run_list_removers ||= []
218
- @run_list_removers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
219
- end
220
- end
221
- end
222
- end
223
-
224
- # Include all recipe objects so require 'cheffish' brings in the whole recipe DSL
225
-
226
- require 'chef/run_list/run_list_item'
227
- require 'cheffish/basic_chef_client'
228
- require 'cheffish/server_api'
229
- require 'chef/knife'
230
- require 'chef/config_fetcher'
231
- require 'chef/log'
232
- require 'chef/application'
233
- require 'cheffish/recipe_dsl'
1
+ module Cheffish
2
+ NAME_REGEX = /^[.\-[:alnum:]_]+$/
3
+
4
+ def self.inline_resource(provider, provider_action, *resources, &block)
5
+ BasicChefClient.inline_resource(provider, provider_action, *resources, &block)
6
+ end
7
+
8
+ def self.default_chef_server(config = profiled_config)
9
+ {
10
+ :chef_server_url => config[:chef_server_url],
11
+ :options => {
12
+ :client_name => config[:node_name],
13
+ :signing_key_filename => config[:client_key]
14
+ }
15
+ }
16
+ end
17
+
18
+ def self.chef_server_api(chef_server = default_chef_server)
19
+ # Pin the server api version to 0 until https://github.com/chef/cheffish/issues/56
20
+ # gets the correct compatibility fix.
21
+ chef_server[:options] ||= {}
22
+ chef_server[:options].merge!(api_version: "0")
23
+ Cheffish::ServerAPI.new(chef_server[:chef_server_url], chef_server[:options])
24
+ end
25
+
26
+ def self.profiled_config(config = Chef::Config)
27
+ if config.profile && config.profiles && config.profiles[config.profile]
28
+ MergedConfig.new(config.profiles[config.profile], config)
29
+ else
30
+ config
31
+ end
32
+ end
33
+
34
+ def self.load_chef_config(chef_config = Chef::Config)
35
+ if ::Gem::Version.new(::Chef::VERSION) >= ::Gem::Version.new('12.0.0')
36
+ chef_config.config_file = ::Chef::Knife.chef_config_dir
37
+ else
38
+ chef_config.config_file = ::Chef::Knife.locate_config_file
39
+ end
40
+ config_fetcher = Chef::ConfigFetcher.new(chef_config.config_file, chef_config.config_file_jail)
41
+ if chef_config.config_file.nil?
42
+ Chef::Log.warn("No config file found or specified on command line, using command line options.")
43
+ elsif config_fetcher.config_missing?
44
+ Chef::Log.warn("Did not find config file: #{chef_config.config_file}, using command line options.")
45
+ else
46
+ config_content = config_fetcher.read_config
47
+ config_file_path = chef_config.config_file
48
+ begin
49
+ chef_config.from_string(config_content, config_file_path)
50
+ rescue Exception => error
51
+ Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
52
+ filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
53
+ filtered_trace.each {|line| Chef::Log.fatal(" " + line )}
54
+ Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
55
+ end
56
+ end
57
+ Cheffish.profiled_config(chef_config)
58
+ end
59
+
60
+ def self.honor_local_mode(local_mode_default = true, &block)
61
+ if !Chef::Config.has_key?(:local_mode) && !local_mode_default.nil?
62
+ Chef::Config.local_mode = local_mode_default
63
+ end
64
+ if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
65
+ Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
66
+ end
67
+ begin
68
+ require 'chef/local_mode'
69
+ Chef::LocalMode.with_server_connectivity(&block)
70
+
71
+ rescue LoadError
72
+ Chef::Application.setup_server_connectivity
73
+ if block_given?
74
+ begin
75
+ yield
76
+ ensure
77
+ Chef::Application.destroy_server_connectivity
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.get_private_key(name, config = profiled_config)
84
+ key, key_path = get_private_key_with_path(name, config)
85
+ key
86
+ end
87
+
88
+ def self.get_private_key_with_path(name, config = profiled_config)
89
+ if config[:private_keys] && config[:private_keys][name]
90
+ named_key = config[:private_keys][name]
91
+ if named_key.is_a?(String)
92
+ Chef::Log.info("Got key #{name} from Chef::Config.private_keys.#{name}, which points at #{named_key}. Reading key from there ...")
93
+ return [ IO.read(named_key), named_key]
94
+ else
95
+ Chef::Log.info("Got key #{name} raw from Chef::Config.private_keys.#{name}.")
96
+ return [ named_key.to_pem, nil ]
97
+ end
98
+ elsif config[:private_key_paths]
99
+ config[:private_key_paths].each do |private_key_path|
100
+ next unless File.exist?(private_key_path)
101
+ Dir.entries(private_key_path).sort.each do |key|
102
+ ext = File.extname(key)
103
+ if ext == '' || ext == '.pem'
104
+ key_name = key[0..-(ext.length+1)]
105
+ if key_name == name
106
+ Chef::Log.info("Reading key #{name} from file #{private_key_path}/#{key}")
107
+ return [ IO.read("#{private_key_path}/#{key}"), "#{private_key_path}/#{key}" ]
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ nil
114
+ end
115
+
116
+ # `NOT_PASSED` is defined in chef-12.5.0, this guard will ensure we
117
+ # don't redefine it if it's already there
118
+ NOT_PASSED=Object.new unless defined?(NOT_PASSED)
119
+
120
+ def self.node_attributes(klass)
121
+ klass.class_eval do
122
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
123
+ attribute :chef_environment, :kind_of => String, :regex => Cheffish::NAME_REGEX
124
+ attribute :run_list, :kind_of => Array # We should let them specify it as a series of parameters too
125
+ attribute :attributes, :kind_of => Hash
126
+
127
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
128
+ # reset to their defaults)
129
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
130
+
131
+ attribute :raw_json, :kind_of => Hash
132
+ attribute :chef_server, :kind_of => Hash
133
+
134
+ # attribute 'ip_address', '127.0.0.1'
135
+ # attribute [ 'pushy', 'port' ], '9000'
136
+ # attribute 'ip_addresses' do |existing_value|
137
+ # (existing_value || []) + [ '127.0.0.1' ]
138
+ # end
139
+ # attribute 'ip_address', :delete
140
+ attr_accessor :attribute_modifiers
141
+ def attribute(attribute_path, value=NOT_PASSED, &block)
142
+ @attribute_modifiers ||= []
143
+ if value != NOT_PASSED
144
+ @attribute_modifiers << [ attribute_path, value ]
145
+ elsif block
146
+ @attribute_modifiers << [ attribute_path, block ]
147
+ else
148
+ raise "attribute requires either a value or a block"
149
+ end
150
+ end
151
+
152
+ # Patchy tags
153
+ # tag 'webserver', 'apache', 'myenvironment'
154
+ def tag(*tags)
155
+ attribute 'tags' do |existing_tags|
156
+ existing_tags ||= []
157
+ tags.each do |tag|
158
+ if !existing_tags.include?(tag.to_s)
159
+ existing_tags << tag.to_s
160
+ end
161
+ end
162
+ existing_tags
163
+ end
164
+ end
165
+ def remove_tag(*tags)
166
+ attribute 'tags' do |existing_tags|
167
+ if existing_tags
168
+ tags.each do |tag|
169
+ existing_tags.delete(tag.to_s)
170
+ end
171
+ end
172
+ existing_tags
173
+ end
174
+ end
175
+
176
+ # NON-patchy tags
177
+ # tags :a, :b, :c # removes all other tags
178
+ def tags(*tags)
179
+ if tags.size == 0
180
+ attribute('tags')
181
+ else
182
+ tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array)
183
+ attribute 'tags', tags.map { |tag| tag.to_s }
184
+ end
185
+ end
186
+
187
+ # Order matters--if two things here are in the wrong order, they will be flipped in the run list
188
+ # recipe 'apache', 'mysql'
189
+ # recipe 'recipe@version'
190
+ # recipe 'recipe'
191
+ # role ''
192
+ attr_accessor :run_list_modifiers
193
+ attr_accessor :run_list_removers
194
+ def recipe(*recipes)
195
+ if recipes.size == 0
196
+ raise ArgumentError, "At least one recipe must be specified"
197
+ end
198
+ @run_list_modifiers ||= []
199
+ @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
200
+ end
201
+ def role(*roles)
202
+ if roles.size == 0
203
+ raise ArgumentError, "At least one role must be specified"
204
+ end
205
+ @run_list_modifiers ||= []
206
+ @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
207
+ end
208
+ def remove_recipe(*recipes)
209
+ if recipes.size == 0
210
+ raise ArgumentError, "At least one recipe must be specified"
211
+ end
212
+ @run_list_removers ||= []
213
+ @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
214
+ end
215
+ def remove_role(*roles)
216
+ if roles.size == 0
217
+ raise ArgumentError, "At least one role must be specified"
218
+ end
219
+ @run_list_removers ||= []
220
+ @run_list_removers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ # Include all recipe objects so require 'cheffish' brings in the whole recipe DSL
227
+
228
+ require 'chef/run_list/run_list_item'
229
+ require 'cheffish/basic_chef_client'
230
+ require 'cheffish/server_api'
231
+ require 'chef/knife'
232
+ require 'chef/config_fetcher'
233
+ require 'chef/log'
234
+ require 'chef/application'
235
+ require 'cheffish/recipe_dsl'