Merge lp:~harlowja/cloud-init/better-chef-module into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Joshua Harlow
Status: Merged
Merged at revision: 1036
Proposed branch: lp:~harlowja/cloud-init/better-chef-module
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 562 lines (+414/-67)
4 files modified
cloudinit/config/cc_chef.py (+247/-58)
cloudinit/util.py (+4/-0)
templates/chef_client.rb.tmpl (+42/-9)
tests/unittests/test_handler/test_handler_chef.py (+121/-0)
To merge this branch: bzr merge lp:~harlowja/cloud-init/better-chef-module
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+238040@code.launchpad.net

Description of the change

Increase the robustness/configurability of the chef module...

Add the following adjustments to the chef template and module:

- Make it so that the chef directories can be provided (defaults
  to the existing directories)
- Make the params much more configurable, and if a parameter is
  provided in the chef configuration it will override existing template
  parameters.
- Make the template skip lines if the values are None in the configuration
  so that template lines can be removed if/when this is desirable.
- Allow the firstboot json path to be configurable (defaults to the
  existing location).
- Adds a basic set of tests to ensure that good things are happening.
- Make a helper function to tell if already installed.
- Have the install routine not run chef after installed but have it instead
  return a result to tell the caller to run the chef program once completed.
- Use the generated_by() utility function to give the ruby template a
  better header comment.
- Set special parameters after selecting the basic chef parameters.
- Allow for the running after install and run arguments to be configured.
- Allow the omnibus url fetching retries to be configurable.
- Move the chef running to its own helper function

To post a comment you must log in.
1027. By Joshua Harlow

Fix newline added at end of file

1028. By Joshua Harlow

Add a few template delete tests

1029. By Joshua Harlow

Move the installation code to its own function

1030. By Joshua Harlow

Some more reworkings

- Make a helper function to tell if already installed.
- Have the install routine not run chef after installed
  but have it instead return a result to tell the caller
  to run the chef program once completed.

1031. By Joshua Harlow

More adjustments

- Use the generated_by() utility function to
  give the ruby template a better header comment
- Set special parameters after selecting the basic
  chef parameters.

1032. By Joshua Harlow

Allow for the running after install and run arguments to be configured

Instead of only running the client when installed from gems, allow it
to be ran from other install modes as well (if configured) and allow the
arguments that are passed to the client when ran to be altered (if so
desired).

1033. By Joshua Harlow

Allow the omnibus url fetching retries to be configurable

Revision history for this message
Jer (jeremiah-wuenschel) :
Revision history for this message
Joshua Harlow (harlowja) :
Revision history for this message
Jer (jeremiah-wuenschel) wrote :

Corrected a diff comment.

1034. By Joshua Harlow

Use the util function to get the chef base directories

1035. By Joshua Harlow

Always ensure we create the /etc/chef dir

1036. By Joshua Harlow

Move the chef running to its own helper function

1037. By Joshua Harlow

Ensure that any template paths have associated directories

When the template provides a path, make sure that before the
template is written that the path that is now in the template
has the associated directory created (if not already created).

1038. By Joshua Harlow

Have the caller find the param paths instead of the param creator

1039. By Joshua Harlow

Follow the same constant variable naming scheme for the path tpl keys

1040. By Joshua Harlow

Allow running even if installed

Standardize on using the chef_cfg key 'exec' which can be used
when installing to tell the caller to run the chef client or can
also be used if the client is already installed and its requested
to be ran.

To retain existing behavior 'exec' does not by default assume to
be true, unless explicitly provided or a gems mode install is
requested.

1041. By Joshua Harlow

Add a comment explaining the param path logic

1042. By Joshua Harlow

Retain the old behavior for mandatory keys

The keys 'server_url' and 'validation_name' were
previously mandatory, we should retain that behavior
for now.

1043. By Joshua Harlow

Add a post-run method that can be used to delete validation.pem files

For those who run chef in non-daemon mode, they would like to delete
the validation.pem file if chef finishes as expected to remove that file
from existing in an easy to read manner.

1044. By Joshua Harlow

Use the key contants in the default key => value set

1045. By Joshua Harlow

Prefer immutable structures

1046. By Joshua Harlow

Update chef module docstring to reflect the new style

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/config/cc_chef.py'
2--- cloudinit/config/cc_chef.py 2014-08-26 18:50:11 +0000
3+++ cloudinit/config/cc_chef.py 2014-11-22 01:13:36 +0000
4@@ -18,6 +18,57 @@
5 # You should have received a copy of the GNU General Public License
6 # along with this program. If not, see <http://www.gnu.org/licenses/>.
7
8+"""
9+**Summary:** module that configures, starts and installs chef.
10+
11+**Description:** This module enables chef to be installed (from packages or
12+from gems, or from omnibus). Before this occurs chef configurations are
13+written to disk (validation.pem, client.pem, firstboot.json, client.rb),
14+and needed chef folders/directories are created (/etc/chef and /var/log/chef
15+and so-on). Then once installing proceeds correctly if configured chef will
16+be started (in daemon mode or in non-daemon mode) and then once that has
17+finished (if ran in non-daemon mode this will be when chef finishes
18+converging, if ran in daemon mode then no further actions are possible since
19+chef will have forked into its own process) then a post run function can
20+run that can do finishing activities (such as removing the validation pem
21+file).
22+
23+It can be configured with the following option structure::
24+
25+ chef:
26+ directories: (defaulting to /etc/chef, /var/log/chef, /var/lib/chef,
27+ /var/cache/chef, /var/backups/chef, /var/run/chef)
28+ validation_key or validation_cert: (optional string to be written to
29+ /etc/chef/validation.pem)
30+ firstboot_path: (path to write run_list and initial_attributes keys that
31+ should also be present in this configuration, defaults
32+ to /etc/chef/firstboot.json)
33+ exec: boolean to run or not run chef (defaults to false, unless
34+ a gem installed is requested
35+ where this will then default
36+ to true)
37+
38+ chef.rb template keys (if falsey, then will be skipped and not
39+ written to /etc/chef/client.rb)
40+
41+ chef:
42+ client_key:
43+ environment:
44+ file_backup_path:
45+ file_cache_path:
46+ json_attribs:
47+ log_level:
48+ log_location:
49+ node_name:
50+ pid_file:
51+ server_url:
52+ show_time:
53+ ssl_verify_mode:
54+ validation_key:
55+ validation_name:
56+"""
57+
58+import itertools
59 import json
60 import os
61
62@@ -27,16 +78,107 @@
63
64 RUBY_VERSION_DEFAULT = "1.8"
65
66-CHEF_DIRS = [
67+CHEF_DIRS = tuple([
68 '/etc/chef',
69 '/var/log/chef',
70 '/var/lib/chef',
71 '/var/cache/chef',
72 '/var/backups/chef',
73 '/var/run/chef',
74-]
75+])
76+REQUIRED_CHEF_DIRS = tuple([
77+ '/etc/chef',
78+])
79
80 OMNIBUS_URL = "https://www.opscode.com/chef/install.sh"
81+OMNIBUS_URL_RETRIES = 5
82+
83+CHEF_VALIDATION_PEM_PATH = '/etc/chef/validation.pem'
84+CHEF_FB_PATH = '/etc/chef/firstboot.json'
85+CHEF_RB_TPL_DEFAULTS = {
86+ # These are ruby symbols...
87+ 'ssl_verify_mode': ':verify_none',
88+ 'log_level': ':info',
89+ # These are not symbols...
90+ 'log_location': '/var/log/chef/client.log',
91+ 'validation_key': CHEF_VALIDATION_PEM_PATH,
92+ 'client_key': "/etc/chef/client.pem",
93+ 'json_attribs': CHEF_FB_PATH,
94+ 'file_cache_path': "/var/cache/chef",
95+ 'file_backup_path': "/var/backups/chef",
96+ 'pid_file': "/var/run/chef/client.pid",
97+ 'show_time': True,
98+}
99+CHEF_RB_TPL_BOOL_KEYS = frozenset(['show_time'])
100+CHEF_RB_TPL_PATH_KEYS = frozenset([
101+ 'log_location',
102+ 'validation_key',
103+ 'client_key',
104+ 'file_cache_path',
105+ 'json_attribs',
106+ 'file_cache_path',
107+ 'pid_file',
108+])
109+CHEF_RB_TPL_KEYS = list(CHEF_RB_TPL_DEFAULTS.keys())
110+CHEF_RB_TPL_KEYS.extend(CHEF_RB_TPL_BOOL_KEYS)
111+CHEF_RB_TPL_KEYS.extend(CHEF_RB_TPL_PATH_KEYS)
112+CHEF_RB_TPL_KEYS.extend([
113+ 'server_url',
114+ 'node_name',
115+ 'environment',
116+ 'validation_name',
117+])
118+CHEF_RB_TPL_KEYS = frozenset(CHEF_RB_TPL_KEYS)
119+CHEF_RB_PATH = '/etc/chef/client.rb'
120+CHEF_EXEC_PATH = '/usr/bin/chef-client'
121+CHEF_EXEC_DEF_ARGS = tuple(['-d', '-i', '1800', '-s', '20'])
122+
123+
124+def is_installed():
125+ if not os.path.isfile(CHEF_EXEC_PATH):
126+ return False
127+ if not os.access(CHEF_EXEC_PATH, os.X_OK):
128+ return False
129+ return True
130+
131+
132+def post_run_chef(chef_cfg, log):
133+ delete_pem = util.get_cfg_option_bool(chef_cfg,
134+ 'delete_validation_post_exec',
135+ default=False)
136+ if delete_pem and os.path.isfile(CHEF_VALIDATION_PEM_PATH):
137+ os.unlink(CHEF_VALIDATION_PEM_PATH)
138+
139+
140+def get_template_params(iid, chef_cfg, log):
141+ params = CHEF_RB_TPL_DEFAULTS.copy()
142+ # Allow users to overwrite any of the keys they want (if they so choose),
143+ # when a value is None, then the value will be set to None and no boolean
144+ # or string version will be populated...
145+ for (k, v) in chef_cfg.items():
146+ if k not in CHEF_RB_TPL_KEYS:
147+ log.debug("Skipping unknown chef template key '%s'", k)
148+ continue
149+ if v is None:
150+ params[k] = None
151+ else:
152+ # This will make the value a boolean or string...
153+ if k in CHEF_RB_TPL_BOOL_KEYS:
154+ params[k] = util.get_cfg_option_bool(chef_cfg, k)
155+ else:
156+ params[k] = util.get_cfg_option_str(chef_cfg, k)
157+ # These ones are overwritten to be exact values...
158+ params.update({
159+ 'generated_by': util.make_header(),
160+ 'node_name': util.get_cfg_option_str(chef_cfg, 'node_name',
161+ default=iid),
162+ 'environment': util.get_cfg_option_str(chef_cfg, 'environment',
163+ default='_default'),
164+ # These two are mandatory...
165+ 'server_url': chef_cfg['server_url'],
166+ 'validation_name': chef_cfg['validation_name'],
167+ })
168+ return params
169
170
171 def handle(name, cfg, cloud, log, _args):
172@@ -49,7 +191,10 @@
173 chef_cfg = cfg['chef']
174
175 # Ensure the chef directories we use exist
176- for d in CHEF_DIRS:
177+ chef_dirs = util.get_cfg_option_list(chef_cfg, 'directories')
178+ if not chef_dirs:
179+ chef_dirs = list(CHEF_DIRS)
180+ for d in itertools.chain(chef_dirs, REQUIRED_CHEF_DIRS):
181 util.ensure_dir(d)
182
183 # Set the validation key based on the presence of either 'validation_key'
184@@ -57,64 +202,108 @@
185 # takes precedence
186 for key in ('validation_key', 'validation_cert'):
187 if key in chef_cfg and chef_cfg[key]:
188- util.write_file('/etc/chef/validation.pem', chef_cfg[key])
189+ util.write_file(CHEF_VALIDATION_PEM_PATH, chef_cfg[key])
190 break
191
192 # Create the chef config from template
193 template_fn = cloud.get_template_filename('chef_client.rb')
194 if template_fn:
195 iid = str(cloud.datasource.get_instance_id())
196- params = {
197- 'server_url': chef_cfg['server_url'],
198- 'node_name': util.get_cfg_option_str(chef_cfg, 'node_name', iid),
199- 'environment': util.get_cfg_option_str(chef_cfg, 'environment',
200- '_default'),
201- 'validation_name': chef_cfg['validation_name']
202- }
203- templater.render_to_file(template_fn, '/etc/chef/client.rb', params)
204- else:
205- log.warn("No template found, not rendering to /etc/chef/client.rb")
206-
207- # set the firstboot json
208- initial_json = {}
209- if 'run_list' in chef_cfg:
210- initial_json['run_list'] = chef_cfg['run_list']
211- if 'initial_attributes' in chef_cfg:
212- initial_attributes = chef_cfg['initial_attributes']
213- for k in list(initial_attributes.keys()):
214- initial_json[k] = initial_attributes[k]
215- util.write_file('/etc/chef/firstboot.json', json.dumps(initial_json))
216-
217+ params = get_template_params(iid, chef_cfg, log)
218+ # Do a best effort attempt to ensure that the template values that
219+ # are associated with paths have there parent directory created
220+ # before they are used by the chef-client itself.
221+ param_paths = set()
222+ for (k, v) in params.items():
223+ if k in CHEF_RB_TPL_PATH_KEYS and v:
224+ param_paths.add(os.path.dirname(v))
225+ util.ensure_dirs(param_paths)
226+ templater.render_to_file(template_fn, CHEF_RB_PATH, params)
227+ else:
228+ log.warn("No template found, not rendering to %s",
229+ CHEF_RB_PATH)
230+
231+ # Set the firstboot json
232+ fb_filename = util.get_cfg_option_str(chef_cfg, 'firstboot_path',
233+ default=CHEF_FB_PATH)
234+ if not fb_filename:
235+ log.info("First boot path empty, not writing first boot json file")
236+ else:
237+ initial_json = {}
238+ if 'run_list' in chef_cfg:
239+ initial_json['run_list'] = chef_cfg['run_list']
240+ if 'initial_attributes' in chef_cfg:
241+ initial_attributes = chef_cfg['initial_attributes']
242+ for k in list(initial_attributes.keys()):
243+ initial_json[k] = initial_attributes[k]
244+ util.write_file(fb_filename, json.dumps(initial_json))
245+
246+ # Try to install chef, if its not already installed...
247+ force_install = util.get_cfg_option_bool(chef_cfg,
248+ 'force_install', default=False)
249+ if not is_installed() or force_install:
250+ run = install_chef(cloud, chef_cfg, log)
251+ elif is_installed():
252+ run = util.get_cfg_option_bool(chef_cfg, 'exec', default=False)
253+ else:
254+ run = False
255+ if run:
256+ run_chef(chef_cfg, log)
257+ post_run_chef(chef_cfg, log)
258+
259+
260+def run_chef(chef_cfg, log):
261+ log.debug('Running chef-client')
262+ cmd = [CHEF_EXEC_PATH]
263+ if 'exec_arguments' in chef_cfg:
264+ cmd_args = chef_cfg['exec_arguments']
265+ if isinstance(cmd_args, (list, tuple)):
266+ cmd.extend(cmd_args)
267+ elif isinstance(cmd_args, (str, basestring)):
268+ cmd.append(cmd_args)
269+ else:
270+ log.warn("Unknown type %s provided for chef"
271+ " 'exec_arguments' expected list, tuple,"
272+ " or string", type(cmd_args))
273+ cmd.extend(CHEF_EXEC_DEF_ARGS)
274+ else:
275+ cmd.extend(CHEF_EXEC_DEF_ARGS)
276+ util.subp(cmd, capture=False)
277+
278+
279+def install_chef(cloud, chef_cfg, log):
280 # If chef is not installed, we install chef based on 'install_type'
281- if (not os.path.isfile('/usr/bin/chef-client') or
282- util.get_cfg_option_bool(chef_cfg,
283- 'force_install', default=False)):
284-
285- install_type = util.get_cfg_option_str(chef_cfg, 'install_type',
286- 'packages')
287- if install_type == "gems":
288- # this will install and run the chef-client from gems
289- chef_version = util.get_cfg_option_str(chef_cfg, 'version', None)
290- ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version',
291- RUBY_VERSION_DEFAULT)
292- install_chef_from_gems(cloud.distro, ruby_version, chef_version)
293- # and finally, run chef-client
294- log.debug('Running chef-client')
295- util.subp(['/usr/bin/chef-client',
296- '-d', '-i', '1800', '-s', '20'], capture=False)
297- elif install_type == 'packages':
298- # this will install and run the chef-client from packages
299- cloud.distro.install_packages(('chef',))
300- elif install_type == 'omnibus':
301- url = util.get_cfg_option_str(chef_cfg, "omnibus_url", OMNIBUS_URL)
302- content = url_helper.readurl(url=url, retries=5)
303- with util.tempdir() as tmpd:
304- # use tmpd over tmpfile to avoid 'Text file busy' on execute
305- tmpf = "%s/chef-omnibus-install" % tmpd
306- util.write_file(tmpf, str(content), mode=0700)
307- util.subp([tmpf], capture=False)
308- else:
309- log.warn("Unknown chef install type %s", install_type)
310+ install_type = util.get_cfg_option_str(chef_cfg, 'install_type',
311+ 'packages')
312+ run = util.get_cfg_option_bool(chef_cfg, 'exec', default=False)
313+ if install_type == "gems":
314+ # This will install and run the chef-client from gems
315+ chef_version = util.get_cfg_option_str(chef_cfg, 'version', None)
316+ ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version',
317+ RUBY_VERSION_DEFAULT)
318+ install_chef_from_gems(cloud.distro, ruby_version, chef_version)
319+ # Retain backwards compat, by preferring True instead of False
320+ # when not provided/overriden...
321+ run = util.get_cfg_option_bool(chef_cfg, 'exec', default=True)
322+ elif install_type == 'packages':
323+ # This will install and run the chef-client from packages
324+ cloud.distro.install_packages(('chef',))
325+ elif install_type == 'omnibus':
326+ # This will install as a omnibus unified package
327+ url = util.get_cfg_option_str(chef_cfg, "omnibus_url", OMNIBUS_URL)
328+ retries = max(0, util.get_cfg_option_int(chef_cfg,
329+ "omnibus_url_retries",
330+ default=OMNIBUS_URL_RETRIES))
331+ content = url_helper.readurl(url=url, retries=retries)
332+ with util.tempdir() as tmpd:
333+ # Use tmpdir over tmpfile to avoid 'text file busy' on execute
334+ tmpf = "%s/chef-omnibus-install" % tmpd
335+ util.write_file(tmpf, str(content), mode=0700)
336+ util.subp([tmpf], capture=False)
337+ else:
338+ log.warn("Unknown chef install type '%s'", install_type)
339+ run = False
340+ return run
341
342
343 def get_ruby_packages(version):
344@@ -133,9 +322,9 @@
345 util.sym_link('/usr/bin/ruby%s' % ruby_version, '/usr/bin/ruby')
346 if chef_version:
347 util.subp(['/usr/bin/gem', 'install', 'chef',
348- '-v %s' % chef_version, '--no-ri',
349- '--no-rdoc', '--bindir', '/usr/bin', '-q'], capture=False)
350+ '-v %s' % chef_version, '--no-ri',
351+ '--no-rdoc', '--bindir', '/usr/bin', '-q'], capture=False)
352 else:
353 util.subp(['/usr/bin/gem', 'install', 'chef',
354- '--no-ri', '--no-rdoc', '--bindir',
355- '/usr/bin', '-q'], capture=False)
356+ '--no-ri', '--no-rdoc', '--bindir',
357+ '/usr/bin', '-q'], capture=False)
358
359=== modified file 'cloudinit/util.py'
360--- cloudinit/util.py 2014-10-29 19:39:04 +0000
361+++ cloudinit/util.py 2014-11-22 01:13:36 +0000
362@@ -399,6 +399,10 @@
363 return val
364
365
366+def get_cfg_option_int(yobj, key, default=0):
367+ return int(get_cfg_option_str(yobj, key, default=default))
368+
369+
370 def system_info():
371 return {
372 'platform': platform.platform(),
373
374=== modified file 'templates/chef_client.rb.tmpl'
375--- templates/chef_client.rb.tmpl 2014-03-05 23:05:59 +0000
376+++ templates/chef_client.rb.tmpl 2014-11-22 01:13:36 +0000
377@@ -9,17 +9,50 @@
378 validation_name: XYZ
379 server_url: XYZ
380 -#}
381-log_level :info
382-log_location "/var/log/chef/client.log"
383-ssl_verify_mode :verify_none
384+{{generated_by}}
385+{#
386+The reason these are not in quotes is because they are ruby
387+symbols that will be placed inside here, and not actual strings...
388+#}
389+{% if log_level %}
390+log_level {{log_level}}
391+{% endif %}
392+{% if ssl_verify_mode %}
393+ssl_verify_mode {{ssl_verify_mode}}
394+{% endif %}
395+{% if log_location %}
396+log_location "{{log_location}}"
397+{% endif %}
398+{% if validation_name %}
399 validation_client_name "{{validation_name}}"
400-validation_key "/etc/chef/validation.pem"
401-client_key "/etc/chef/client.pem"
402+{% endif %}
403+{% if validation_key %}
404+validation_key "{{validation_key}}"
405+{% endif %}
406+{% if client_key %}
407+client_key "{{client_key}}"
408+{% endif %}
409+{% if server_url %}
410 chef_server_url "{{server_url}}"
411+{% endif %}
412+{% if environment %}
413 environment "{{environment}}"
414+{% endif %}
415+{% if node_name %}
416 node_name "{{node_name}}"
417-json_attribs "/etc/chef/firstboot.json"
418-file_cache_path "/var/cache/chef"
419-file_backup_path "/var/backups/chef"
420-pid_file "/var/run/chef/client.pid"
421+{% endif %}
422+{% if json_attribs %}
423+json_attribs "{{json_attribs}}"
424+{% endif %}
425+{% if file_cache_path %}
426+file_cache_path "{{file_cache_path}}"
427+{% endif %}
428+{% if file_backup_path %}
429+file_backup_path "{{file_backup_path}}"
430+{% endif %}
431+{% if pid_file %}
432+pid_file "{{pid_file}}"
433+{% endif %}
434+{% if show_time %}
435 Chef::Log::Formatter.show_time = true
436+{% endif %}
437
438=== added file 'tests/unittests/test_handler/test_handler_chef.py'
439--- tests/unittests/test_handler/test_handler_chef.py 1970-01-01 00:00:00 +0000
440+++ tests/unittests/test_handler/test_handler_chef.py 2014-11-22 01:13:36 +0000
441@@ -0,0 +1,121 @@
442+import json
443+import os
444+
445+from cloudinit.config import cc_chef
446+
447+from cloudinit import cloud
448+from cloudinit import distros
449+from cloudinit import helpers
450+from cloudinit import util
451+from cloudinit.sources import DataSourceNone
452+
453+from .. import helpers as t_help
454+
455+import logging
456+
457+LOG = logging.getLogger(__name__)
458+
459+
460+class TestChef(t_help.FilesystemMockingTestCase):
461+ def setUp(self):
462+ super(TestChef, self).setUp()
463+ self.tmp = self.makeDir(prefix="unittest_")
464+
465+ def fetch_cloud(self, distro_kind):
466+ cls = distros.fetch(distro_kind)
467+ paths = helpers.Paths({})
468+ distro = cls(distro_kind, {}, paths)
469+ ds = DataSourceNone.DataSourceNone({}, distro, paths, None)
470+ return cloud.Cloud(ds, paths, {}, distro, None)
471+
472+ def test_no_config(self):
473+ self.patchUtils(self.tmp)
474+ self.patchOS(self.tmp)
475+
476+ cfg = {}
477+ cc_chef.handle('chef', cfg, self.fetch_cloud('ubuntu'), LOG, [])
478+ for d in cc_chef.CHEF_DIRS:
479+ self.assertFalse(os.path.isdir(d))
480+
481+ def test_basic_config(self):
482+ # This should create a file of the format...
483+ """
484+ # Created by cloud-init v. 0.7.6 on Sat, 11 Oct 2014 23:57:21 +0000
485+ log_level :info
486+ ssl_verify_mode :verify_none
487+ log_location "/var/log/chef/client.log"
488+ validation_client_name "bob"
489+ validation_key "/etc/chef/validation.pem"
490+ client_key "/etc/chef/client.pem"
491+ chef_server_url "localhost"
492+ environment "_default"
493+ node_name "iid-datasource-none"
494+ json_attribs "/etc/chef/firstboot.json"
495+ file_cache_path "/var/cache/chef"
496+ file_backup_path "/var/backups/chef"
497+ pid_file "/var/run/chef/client.pid"
498+ Chef::Log::Formatter.show_time = true
499+ """
500+ tpl_file = util.load_file('templates/chef_client.rb.tmpl')
501+ self.patchUtils(self.tmp)
502+ self.patchOS(self.tmp)
503+
504+ util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file)
505+ cfg = {
506+ 'chef': {
507+ 'server_url': 'localhost',
508+ 'validation_name': 'bob',
509+ },
510+ }
511+ cc_chef.handle('chef', cfg, self.fetch_cloud('ubuntu'), LOG, [])
512+ for d in cc_chef.CHEF_DIRS:
513+ self.assertTrue(os.path.isdir(d))
514+ c = util.load_file(cc_chef.CHEF_RB_PATH)
515+ for k, v in cfg['chef'].items():
516+ self.assertIn(v, c)
517+ for k, v in cc_chef.CHEF_RB_TPL_DEFAULTS.items():
518+ if isinstance(v, basestring):
519+ self.assertIn(v, c)
520+ c = util.load_file(cc_chef.CHEF_FB_PATH)
521+ self.assertEqual({}, json.loads(c))
522+
523+ def test_firstboot_json(self):
524+ self.patchUtils(self.tmp)
525+ self.patchOS(self.tmp)
526+
527+ cfg = {
528+ 'chef': {
529+ 'server_url': 'localhost',
530+ 'validation_name': 'bob',
531+ 'run_list': ['a', 'b', 'c'],
532+ 'initial_attributes': {
533+ 'c': 'd',
534+ }
535+ },
536+ }
537+ cc_chef.handle('chef', cfg, self.fetch_cloud('ubuntu'), LOG, [])
538+ c = util.load_file(cc_chef.CHEF_FB_PATH)
539+ self.assertEqual(
540+ {
541+ 'run_list': ['a', 'b', 'c'],
542+ 'c': 'd',
543+ }, json.loads(c))
544+
545+ def test_template_deletes(self):
546+ tpl_file = util.load_file('templates/chef_client.rb.tmpl')
547+ self.patchUtils(self.tmp)
548+ self.patchOS(self.tmp)
549+
550+ util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file)
551+ cfg = {
552+ 'chef': {
553+ 'server_url': 'localhost',
554+ 'validation_name': 'bob',
555+ 'json_attribs': None,
556+ 'show_time': None,
557+ },
558+ }
559+ cc_chef.handle('chef', cfg, self.fetch_cloud('ubuntu'), LOG, [])
560+ c = util.load_file(cc_chef.CHEF_RB_PATH)
561+ self.assertNotIn('json_attribs', c)
562+ self.assertNotIn('Formatter.show_time', c)