cheffish 1.4.1 → 1.4.2

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 -120
  4. data/Rakefile +23 -23
  5. data/lib/chef/provider/chef_acl.rb +439 -439
  6. data/lib/chef/provider/chef_client.rb +53 -53
  7. data/lib/chef/provider/chef_container.rb +55 -55
  8. data/lib/chef/provider/chef_data_bag.rb +55 -55
  9. data/lib/chef/provider/chef_data_bag_item.rb +278 -278
  10. data/lib/chef/provider/chef_environment.rb +83 -83
  11. data/lib/chef/provider/chef_group.rb +83 -83
  12. data/lib/chef/provider/chef_mirror.rb +169 -169
  13. data/lib/chef/provider/chef_node.rb +87 -87
  14. data/lib/chef/provider/chef_organization.rb +155 -155
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +46 -46
  16. data/lib/chef/provider/chef_role.rb +84 -84
  17. data/lib/chef/provider/chef_user.rb +59 -59
  18. data/lib/chef/provider/private_key.rb +225 -225
  19. data/lib/chef/provider/public_key.rb +88 -88
  20. data/lib/chef/resource/chef_acl.rb +69 -69
  21. data/lib/chef/resource/chef_client.rb +48 -48
  22. data/lib/chef/resource/chef_container.rb +22 -22
  23. data/lib/chef/resource/chef_data_bag.rb +22 -22
  24. data/lib/chef/resource/chef_data_bag_item.rb +121 -121
  25. data/lib/chef/resource/chef_environment.rb +77 -77
  26. data/lib/chef/resource/chef_group.rb +53 -53
  27. data/lib/chef/resource/chef_mirror.rb +52 -52
  28. data/lib/chef/resource/chef_node.rb +22 -22
  29. data/lib/chef/resource/chef_organization.rb +69 -69
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +35 -35
  31. data/lib/chef/resource/chef_role.rb +110 -110
  32. data/lib/chef/resource/chef_user.rb +56 -56
  33. data/lib/chef/resource/private_key.rb +48 -48
  34. data/lib/chef/resource/public_key.rb +25 -25
  35. data/lib/cheffish/actor_provider_base.rb +131 -131
  36. data/lib/cheffish/basic_chef_client.rb +184 -184
  37. data/lib/cheffish/chef_provider_base.rb +246 -246
  38. data/lib/cheffish/chef_run.rb +162 -162
  39. data/lib/cheffish/chef_run_data.rb +19 -19
  40. data/lib/cheffish/chef_run_listener.rb +30 -30
  41. data/lib/cheffish/key_formatter.rb +113 -113
  42. data/lib/cheffish/merged_config.rb +94 -94
  43. data/lib/cheffish/recipe_dsl.rb +157 -157
  44. data/lib/cheffish/rspec/chef_run_support.rb +83 -83
  45. data/lib/cheffish/rspec/matchers/be_idempotent.rb +16 -16
  46. data/lib/cheffish/rspec/matchers/emit_no_warnings_or_errors.rb +15 -15
  47. data/lib/cheffish/rspec/matchers/have_updated.rb +37 -37
  48. data/lib/cheffish/rspec/matchers/partially_match.rb +63 -63
  49. data/lib/cheffish/rspec/matchers.rb +4 -4
  50. data/lib/cheffish/rspec/recipe_run_wrapper.rb +78 -59
  51. data/lib/cheffish/rspec/repository_support.rb +108 -108
  52. data/lib/cheffish/rspec.rb +8 -8
  53. data/lib/cheffish/server_api.rb +52 -52
  54. data/lib/cheffish/version.rb +3 -3
  55. data/lib/cheffish/with_pattern.rb +21 -21
  56. data/lib/cheffish.rb +235 -235
  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 -78
  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 -37
  76. metadata +3 -2
@@ -1,52 +1,52 @@
1
- #
2
- # Author:: John Keiser (<jkeiser@opscode.com>)
3
- # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'chef/version'
20
- require 'chef/http'
21
- require 'chef/http/authenticator'
22
- require 'chef/http/cookie_manager'
23
- require 'chef/http/decompressor'
24
- require 'chef/http/json_input'
25
- require 'chef/http/json_output'
26
- if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
27
- require 'chef/http/remote_request_id'
28
- end
29
-
30
- module Cheffish
31
- # Exactly like Chef::ServerAPI, but requires you to pass in what keys you want (no defaults)
32
- class ServerAPI < Chef::HTTP
33
-
34
- def initialize(url, options = {})
35
- super(url, options)
36
- root_url = URI.parse(url)
37
- root_url.path = ''
38
- @root_url = root_url.to_s
39
- end
40
-
41
- attr_reader :root_url
42
-
43
- use Chef::HTTP::JSONInput
44
- use Chef::HTTP::JSONOutput
45
- use Chef::HTTP::CookieManager
46
- use Chef::HTTP::Decompressor
47
- use Chef::HTTP::Authenticator
48
- if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
49
- use Chef::HTTP::RemoteRequestID
50
- end
51
- end
52
- end
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/version'
20
+ require 'chef/http'
21
+ require 'chef/http/authenticator'
22
+ require 'chef/http/cookie_manager'
23
+ require 'chef/http/decompressor'
24
+ require 'chef/http/json_input'
25
+ require 'chef/http/json_output'
26
+ if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
27
+ require 'chef/http/remote_request_id'
28
+ end
29
+
30
+ module Cheffish
31
+ # Exactly like Chef::ServerAPI, but requires you to pass in what keys you want (no defaults)
32
+ class ServerAPI < Chef::HTTP
33
+
34
+ def initialize(url, options = {})
35
+ super(url, options)
36
+ root_url = URI.parse(url)
37
+ root_url.path = ''
38
+ @root_url = root_url.to_s
39
+ end
40
+
41
+ attr_reader :root_url
42
+
43
+ use Chef::HTTP::JSONInput
44
+ use Chef::HTTP::JSONOutput
45
+ use Chef::HTTP::CookieManager
46
+ use Chef::HTTP::Decompressor
47
+ use Chef::HTTP::Authenticator
48
+ if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
49
+ use Chef::HTTP::RemoteRequestID
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
- module Cheffish
2
- VERSION = '1.4.1'
3
- end
1
+ module Cheffish
2
+ VERSION = '1.4.2'
3
+ end
@@ -1,21 +1,21 @@
1
- module Cheffish
2
- module WithPattern
3
- def with(symbol)
4
- class_eval <<EOM
5
- attr_accessor :current_#{symbol}
6
-
7
- def with_#{symbol}(value)
8
- old_value = self.current_#{symbol}
9
- self.current_#{symbol} = value
10
- if block_given?
11
- begin
12
- yield
13
- ensure
14
- self.current_#{symbol} = old_value
15
- end
16
- end
17
- end
18
- EOM
19
- end
20
- end
21
- end
1
+ module Cheffish
2
+ module WithPattern
3
+ def with(symbol)
4
+ class_eval <<EOM
5
+ attr_accessor :current_#{symbol}
6
+
7
+ def with_#{symbol}(value)
8
+ old_value = self.current_#{symbol}
9
+ self.current_#{symbol} = value
10
+ if block_given?
11
+ begin
12
+ yield
13
+ ensure
14
+ self.current_#{symbol} = old_value
15
+ end
16
+ end
17
+ end
18
+ EOM
19
+ end
20
+ end
21
+ end
data/lib/cheffish.rb CHANGED
@@ -1,235 +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` 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'
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'