Chef-Zero software config hook
Implements a software config hook for using chef-zero, an in-memory, standalone chef service. Chef-Zero has replaced chef-solo as the preferred serverless implementation, therfore this patch replaces the previous chef-solo patch at https://review.openstack.org/#/c/80229/. Change-Id: Ic93b2e67be0c07e27d26ed2c6677c5a0f1b42e07 Closes-Bug: #1279062
This commit is contained in:
parent
2a7fe20efa
commit
2534497008
36
hot/software-config/elements/heat-config-chef/README.rst
Normal file
36
hot/software-config/elements/heat-config-chef/README.rst
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
A hook which invokes ``chef-client`` in local mode (chef zero) on the
|
||||||
|
provided configuration.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
-------
|
||||||
|
Inputs are attribute overrides. In order to format them correctly for
|
||||||
|
consumption, you need to explicitly declare each top-level section as an
|
||||||
|
input of type ``Json`` in your config resource.
|
||||||
|
|
||||||
|
Additionally, there is a special input named ``environment`` of type
|
||||||
|
``String`` that you can use to specify which environment to use when
|
||||||
|
applying the config. You do not have to explicitly declare this input in
|
||||||
|
the config resource.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
--------
|
||||||
|
If you need to capture specific outputs from your chef run, you should
|
||||||
|
specify the output name(s) as normal in your config. Then, your recipes
|
||||||
|
should write files to the directory specified by the ``heat_outputs_path``
|
||||||
|
environment variable. The file name should match the name of the output
|
||||||
|
you are trying to capture.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-------------
|
||||||
|
|
||||||
|
kitchen : optional
|
||||||
|
A URL for a Git repository containing the desired recipes, roles,
|
||||||
|
environments and other configuration.
|
||||||
|
|
||||||
|
This will be cloned into ``kitchen_path`` for use by chef.
|
||||||
|
|
||||||
|
kitchen_path : default ``/var/lib/heat-config/heat-config-chef/kitchen``
|
||||||
|
Instance-local path for the recipes, roles, environments, etc.
|
||||||
|
|
||||||
|
If ``kitchen`` is not specified, this directory must be populated via
|
||||||
|
user-data, another software config, or other "manual" method.
|
@ -0,0 +1 @@
|
|||||||
|
heat-config
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -x
|
||||||
|
|
||||||
|
SCRIPTDIR=$(dirname $0)
|
||||||
|
|
||||||
|
install-packages chef git
|
||||||
|
install -D -g root -o root -m 0755 ${SCRIPTDIR}/hook-chef.py /var/lib/heat-config/hooks/chef
|
161
hot/software-config/elements/heat-config-chef/install.d/hook-chef.py
Executable file
161
hot/software-config/elements/heat-config-chef/install.d/hook-chef.py
Executable file
@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import six
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DEPLOY_KEYS = ("deploy_server_id",
|
||||||
|
"deploy_action",
|
||||||
|
"deploy_stack_id",
|
||||||
|
"deploy_resource_name",
|
||||||
|
"deploy_signal_transport",
|
||||||
|
"deploy_signal_id",
|
||||||
|
"deploy_signal_verb")
|
||||||
|
WORKING_DIR = os.environ.get('HEAT_CHEF_WORKING',
|
||||||
|
'/var/lib/heat-config/heat-config-chef')
|
||||||
|
OUTPUTS_DIR = os.environ.get('HEAT_CHEF_OUTPUTS',
|
||||||
|
'/var/run/heat-config/heat-config-chef')
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_dir(path):
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
os.makedirs(path, 0o700)
|
||||||
|
|
||||||
|
|
||||||
|
def run_subproc(fn, **kwargs):
|
||||||
|
env = os.environ.copy()
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
env[six.text_type(k)] = v
|
||||||
|
try:
|
||||||
|
subproc = subprocess.Popen(fn, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
env=env)
|
||||||
|
stdout, stderr = subproc.communicate()
|
||||||
|
except OSError as exc:
|
||||||
|
ret = -1
|
||||||
|
stderr = six.text_type(exc)
|
||||||
|
stdout = ""
|
||||||
|
else:
|
||||||
|
ret = subproc.returncode
|
||||||
|
if not ret:
|
||||||
|
ret = 0
|
||||||
|
return ret, stdout, stderr
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
log = logging.getLogger('heat-config')
|
||||||
|
handler = logging.StreamHandler(sys.stderr)
|
||||||
|
handler.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
|
'[%(asctime)s] (%(name)s) [%(levelname)s] %(message)s'))
|
||||||
|
log.addHandler(handler)
|
||||||
|
log.setLevel('DEBUG')
|
||||||
|
|
||||||
|
prepare_dir(OUTPUTS_DIR)
|
||||||
|
prepare_dir(WORKING_DIR)
|
||||||
|
os.chdir(WORKING_DIR)
|
||||||
|
|
||||||
|
c = json.load(sys.stdin)
|
||||||
|
|
||||||
|
client_config = ("log_level :debug\n"
|
||||||
|
"log_location STDOUT\n"
|
||||||
|
"local_mode true\n"
|
||||||
|
"chef_zero.enabled true")
|
||||||
|
|
||||||
|
# configure/set up the kitchen
|
||||||
|
kitchen = c['options'].get('kitchen')
|
||||||
|
kitchen_path = c['options'].get('kitchen_path', os.path.join(WORKING_DIR,
|
||||||
|
"kitchen"))
|
||||||
|
cookbook_path = os.path.join(kitchen_path, "cookbooks")
|
||||||
|
role_path = os.path.join(kitchen_path, "roles")
|
||||||
|
environment_path = os.path.join(kitchen_path, "environments")
|
||||||
|
client_config += "\ncookbook_path '%s'" % cookbook_path
|
||||||
|
client_config += "\nrole_path '%s'" % role_path
|
||||||
|
client_config += "\nenvironment_path '%s'" % environment_path
|
||||||
|
if kitchen:
|
||||||
|
log.debug("Cloning kitchen from %s", kitchen)
|
||||||
|
# remove the existing kitchen on update so we get a fresh clone
|
||||||
|
dep_action = next((input['value'] for input in c['inputs']
|
||||||
|
if input['name'] == "deploy_action"), None)
|
||||||
|
if dep_action == "UPDATE":
|
||||||
|
shutil.rmtree(kitchen_path, ignore_errors=True)
|
||||||
|
cmd = ["git", "clone", kitchen, kitchen_path]
|
||||||
|
ret, out, err = run_subproc(cmd)
|
||||||
|
if ret != 0:
|
||||||
|
log.error("Error cloning kitchen from %s into %s: %s", kitchen,
|
||||||
|
kitchen_path, err)
|
||||||
|
json.dump({'deploy_status_code': ret,
|
||||||
|
'deploy_stdout': out,
|
||||||
|
'deploy_stderr': err},
|
||||||
|
sys.stdout)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# write the json attributes
|
||||||
|
ret, out, err = run_subproc(['hostname', '-f'])
|
||||||
|
if ret == 0:
|
||||||
|
fqdn = out.strip()
|
||||||
|
else:
|
||||||
|
err = "Could not determine hostname with hostname -f"
|
||||||
|
json.dump({'deploy_status_code': ret,
|
||||||
|
'deploy_stdout': "",
|
||||||
|
'deploy_stderr': err}, sys.stdout)
|
||||||
|
return 0
|
||||||
|
node_config = {}
|
||||||
|
for input in c['inputs']:
|
||||||
|
if input['name'] == 'environment':
|
||||||
|
client_config += "\nenvironment '%s'" % input['value']
|
||||||
|
elif input['name'] not in DEPLOY_KEYS:
|
||||||
|
node_config.update({input['name']: input['value']})
|
||||||
|
node_config.update({"run_list": json.loads(c['config'])})
|
||||||
|
node_path = os.path.join(WORKING_DIR, "node")
|
||||||
|
prepare_dir(node_path)
|
||||||
|
node_file = os.path.join(node_path, "%s.json" % fqdn)
|
||||||
|
with os.fdopen(os.open(node_file, os.O_CREAT | os.O_WRONLY, 0o600),
|
||||||
|
'w') as f:
|
||||||
|
f.write(json.dumps(node_config, indent=4))
|
||||||
|
client_config += "\nnode_path '%s'" % node_path
|
||||||
|
|
||||||
|
# write out the completed client config
|
||||||
|
config_path = os.path.join(WORKING_DIR, "client.rb")
|
||||||
|
with os.fdopen(os.open(config_path, os.O_CREAT | os.O_WRONLY, 0o600),
|
||||||
|
'w') as f:
|
||||||
|
f.write(client_config)
|
||||||
|
|
||||||
|
# run chef
|
||||||
|
heat_outputs_path = os.path.join(OUTPUTS_DIR, c['id'])
|
||||||
|
cmd = ['chef-client', '-z', '--config', config_path, "-j", node_file]
|
||||||
|
ret, out, err = run_subproc(cmd, heat_outputs_path=heat_outputs_path)
|
||||||
|
resp = {'deploy_status_code': ret,
|
||||||
|
'deploy_stdout': out,
|
||||||
|
'deploy_stderr': err}
|
||||||
|
log.debug("Chef output: %s", out)
|
||||||
|
if err:
|
||||||
|
log.error("Chef return code %s:\n%s", ret, err)
|
||||||
|
for output in c.get('outputs', []):
|
||||||
|
output_name = output['name']
|
||||||
|
try:
|
||||||
|
with open('%s.%s' % (heat_outputs_path, output_name)) as out:
|
||||||
|
resp[output_name] = out.read()
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
json.dump(resp, sys.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv))
|
@ -0,0 +1,76 @@
|
|||||||
|
heat_template_version: 2014-10-16
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
key_name:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
flavor:
|
||||||
|
type: string
|
||||||
|
default: m1.small
|
||||||
|
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
default: fedora-software-config
|
||||||
|
|
||||||
|
resources:
|
||||||
|
|
||||||
|
the_sg:
|
||||||
|
type: OS::Neutron::SecurityGroup
|
||||||
|
properties:
|
||||||
|
name: the_sg
|
||||||
|
description: Ping and SSH
|
||||||
|
rules:
|
||||||
|
- protocol: icmp
|
||||||
|
- protocol: tcp
|
||||||
|
port_range_min: 22
|
||||||
|
port_range_max: 22
|
||||||
|
|
||||||
|
config:
|
||||||
|
type: OS::Heat::StructuredConfig
|
||||||
|
properties:
|
||||||
|
group: chef
|
||||||
|
inputs:
|
||||||
|
- name: nginx-pkg
|
||||||
|
type: Json
|
||||||
|
config:
|
||||||
|
- "recipe[nginx-pkg]"
|
||||||
|
options:
|
||||||
|
kitchen: "https://github.com/st-isidore-de-seville/cookbook-nginx-pkg.git"
|
||||||
|
kitchen_path: "/opt/heat/chef"
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
type: OS::Heat::StructuredDeployment
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
get_resource: structured_config
|
||||||
|
server:
|
||||||
|
get_resource: server
|
||||||
|
input_values:
|
||||||
|
nginx-pkg:
|
||||||
|
package:
|
||||||
|
name: nginx
|
||||||
|
|
||||||
|
server:
|
||||||
|
type: OS::Nova::Server
|
||||||
|
properties:
|
||||||
|
image: {get_param: image}
|
||||||
|
flavor: {get_param: flavor}
|
||||||
|
key_name: {get_param: key_name}
|
||||||
|
security_groups:
|
||||||
|
- {get_resource: the_sg}
|
||||||
|
user_data_format: SOFTWARE_CONFIG
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
|
||||||
|
status_code_deployment:
|
||||||
|
value:
|
||||||
|
get_attr: [deployment, deploy_status_code]
|
||||||
|
|
||||||
|
stdout:
|
||||||
|
value:
|
||||||
|
get_attr: [deployment, deploy_stdout]
|
||||||
|
|
||||||
|
stderr:
|
||||||
|
value:
|
||||||
|
get_attr: [deployment, deploy_stderr]
|
207
tests/software_config/test_hook_chef.py
Normal file
207
tests/software_config/test_hook_chef.py
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import imp
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import mock
|
||||||
|
import StringIO
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from tests.software_config import common
|
||||||
|
|
||||||
|
log = logging.getLogger('test_hook_chef')
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("os.chdir")
|
||||||
|
@mock.patch("os.makedirs")
|
||||||
|
@mock.patch('subprocess.Popen')
|
||||||
|
class HookChefTest(common.RunScriptTest):
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'id': 'fake_stack',
|
||||||
|
'name': 'fake_resource_name',
|
||||||
|
'group': 'chef',
|
||||||
|
'inputs': [
|
||||||
|
{'name': 'fooval', 'value': {'bar': 'baz'}},
|
||||||
|
{'name': 'barval', 'value': {'foo': 'biff'}},
|
||||||
|
{'name': "deploy_server_id", 'value': 'foo'},
|
||||||
|
{'name': "deploy_action", 'value': 'foo'},
|
||||||
|
{'name': "deploy_stack_id", 'value': 'foo'},
|
||||||
|
{'name': "deploy_resource_name", 'value': 'foo'},
|
||||||
|
{'name': "deploy_signal_transport", 'value': 'foo'},
|
||||||
|
{'name': "deploy_signal_id", 'value': 'foo'},
|
||||||
|
{'name': "deploy_signal_verb", 'value': 'foo'}
|
||||||
|
],
|
||||||
|
'options': {},
|
||||||
|
'outputs': [
|
||||||
|
{'name': 'first_output'},
|
||||||
|
{'name': 'second_output'}
|
||||||
|
],
|
||||||
|
'config': None
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(HookChefTest, self).setUp()
|
||||||
|
self.hook_path = self.relative_path(
|
||||||
|
__file__,
|
||||||
|
'../..',
|
||||||
|
'hot/software-config/elements',
|
||||||
|
'heat-config-chef/install.d/hook-chef.py')
|
||||||
|
sys.stdin = StringIO.StringIO()
|
||||||
|
sys.stdout = StringIO.StringIO()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(HookChefTest, self).tearDown()
|
||||||
|
sys.stdin = sys.__stdin__
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
|
||||||
|
def get_module(self):
|
||||||
|
try:
|
||||||
|
imp.acquire_lock()
|
||||||
|
return imp.load_source("hook_chef", self.hook_path)
|
||||||
|
finally:
|
||||||
|
imp.release_lock()
|
||||||
|
|
||||||
|
def test_hook(self, mock_popen, mock_mkdirs, mock_chdir):
|
||||||
|
data = copy.deepcopy(self.data)
|
||||||
|
data['config'] = '["recipe[apache]"]'
|
||||||
|
hook_chef = self.get_module()
|
||||||
|
sys.stdin.write(json.dumps(data))
|
||||||
|
sys.stdin.seek(0)
|
||||||
|
mock_subproc = mock.Mock()
|
||||||
|
mock_popen.return_value = mock_subproc
|
||||||
|
mock_subproc.communicate.return_value = ("out", "err")
|
||||||
|
mock_subproc.returncode = 0
|
||||||
|
with mock.patch("os.fdopen", mock.mock_open()) as mfdopen:
|
||||||
|
with mock.patch("os.open", mock.mock_open()):
|
||||||
|
hook_chef.main(json.dumps(data))
|
||||||
|
exp_node = {
|
||||||
|
'barval': {'foo': 'biff'},
|
||||||
|
'fooval': {u'bar': u'baz'},
|
||||||
|
'run_list': [u'recipe[apache]']
|
||||||
|
}
|
||||||
|
exp_node = json.dumps(exp_node, indent=4)
|
||||||
|
exp_cfg = ("log_level :debug\n"
|
||||||
|
"log_location STDOUT\n"
|
||||||
|
"local_mode true\n"
|
||||||
|
"chef_zero.enabled true\n"
|
||||||
|
"cookbook_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/cookbooks'\n"
|
||||||
|
"role_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/roles'\n"
|
||||||
|
"environment_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/environments'\n"
|
||||||
|
"node_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/node'")
|
||||||
|
mfdopen.return_value.write.assert_any_call(exp_cfg)
|
||||||
|
mfdopen.return_value.write.assert_any_call(exp_node)
|
||||||
|
calls = [
|
||||||
|
mock.call(['hostname', '-f'], env=mock.ANY, stderr=mock.ANY,
|
||||||
|
stdout=mock.ANY),
|
||||||
|
mock.call([
|
||||||
|
'chef-client', '-z', '--config',
|
||||||
|
'/var/lib/heat-config/heat-config-chef/client.rb', '-j',
|
||||||
|
'/var/lib/heat-config/heat-config-chef/node/out.json'],
|
||||||
|
env=mock.ANY, stderr=mock.ANY, stdout=mock.ANY)
|
||||||
|
]
|
||||||
|
mock_popen.assert_has_calls(calls, any_order=True)
|
||||||
|
self.assertEqual({"deploy_status_code": 0,
|
||||||
|
"deploy_stdout": "out",
|
||||||
|
"deploy_stderr": "err"},
|
||||||
|
json.loads(sys.stdout.getvalue()))
|
||||||
|
|
||||||
|
def test_hook_with_kitchen(self, mock_popen, mock_mkdirs, mock_chdir):
|
||||||
|
data = copy.deepcopy(self.data)
|
||||||
|
data['config'] = '["recipe[apache]"]'
|
||||||
|
data['options'] = {
|
||||||
|
"kitchen": "https://github.com/fake.git",
|
||||||
|
"kitchen_path": "/opt/heat/chef/kitchen"
|
||||||
|
}
|
||||||
|
sys.stdin.write(json.dumps(data))
|
||||||
|
hook_chef = self.get_module()
|
||||||
|
sys.stdin.seek(0)
|
||||||
|
mock_subproc = mock.Mock()
|
||||||
|
mock_popen.return_value = mock_subproc
|
||||||
|
mock_subproc.communicate.return_value = ("out", "err")
|
||||||
|
mock_subproc.returncode = 0
|
||||||
|
with mock.patch("os.fdopen", mock.mock_open()) as mfdopen:
|
||||||
|
with mock.patch("os.open", mock.mock_open()):
|
||||||
|
hook_chef.main(json.dumps(data))
|
||||||
|
exp_cfg = ("log_level :debug\n"
|
||||||
|
"log_location STDOUT\n"
|
||||||
|
"local_mode true\n"
|
||||||
|
"chef_zero.enabled true\n"
|
||||||
|
"cookbook_path '/opt/heat/chef/kitchen/"
|
||||||
|
"cookbooks'\n"
|
||||||
|
"role_path '/opt/heat/chef/kitchen/roles'\n"
|
||||||
|
"environment_path '/opt/heat/chef/kitchen/"
|
||||||
|
"environments'\n"
|
||||||
|
"node_path '/var/lib/heat-config/heat-config-chef"
|
||||||
|
"/node'")
|
||||||
|
mfdopen.return_value.write.assert_any_call(exp_cfg)
|
||||||
|
calls = [
|
||||||
|
mock.call(['git', 'clone', "https://github.com/fake.git",
|
||||||
|
"/opt/heat/chef/kitchen"], env=mock.ANY,
|
||||||
|
stderr=mock.ANY, stdout=mock.ANY),
|
||||||
|
mock.call(['hostname', '-f'], env=mock.ANY, stderr=mock.ANY,
|
||||||
|
stdout=mock.ANY),
|
||||||
|
mock.call([
|
||||||
|
'chef-client', '-z', '--config',
|
||||||
|
'/var/lib/heat-config/heat-config-chef/client.rb', '-j',
|
||||||
|
'/var/lib/heat-config/heat-config-chef/node/out.json'],
|
||||||
|
env=mock.ANY, stderr=mock.ANY, stdout=mock.ANY)
|
||||||
|
]
|
||||||
|
mock_popen.assert_has_calls(calls, any_order=True)
|
||||||
|
self.assertEqual({"deploy_status_code": 0,
|
||||||
|
"deploy_stdout": "out",
|
||||||
|
"deploy_stderr": "err"},
|
||||||
|
json.loads(sys.stdout.getvalue()))
|
||||||
|
|
||||||
|
def test_hook_environment(self, mock_popen, mock_mkdirs, mock_chdir):
|
||||||
|
data = copy.deepcopy(self.data)
|
||||||
|
data['config'] = '["recipe[apache]"]'
|
||||||
|
data['inputs'].append({'name': 'environment',
|
||||||
|
'value': 'production'})
|
||||||
|
hook_chef = self.get_module()
|
||||||
|
sys.stdin.write(json.dumps(data))
|
||||||
|
sys.stdin.seek(0)
|
||||||
|
mock_subproc = mock.Mock()
|
||||||
|
mock_popen.return_value = mock_subproc
|
||||||
|
mock_subproc.communicate.return_value = ("out", "err")
|
||||||
|
mock_subproc.returncode = 0
|
||||||
|
with mock.patch("os.fdopen", mock.mock_open()) as mfdopen:
|
||||||
|
with mock.patch("os.open", mock.mock_open()):
|
||||||
|
hook_chef.main(json.dumps(data))
|
||||||
|
exp_node = {
|
||||||
|
'barval': {'foo': 'biff'},
|
||||||
|
'fooval': {u'bar': u'baz'},
|
||||||
|
'run_list': [u'recipe[apache]']
|
||||||
|
}
|
||||||
|
exp_node = json.dumps(exp_node, indent=4)
|
||||||
|
exp_cfg = ("log_level :debug\n"
|
||||||
|
"log_location STDOUT\n"
|
||||||
|
"local_mode true\n"
|
||||||
|
"chef_zero.enabled true\n"
|
||||||
|
"cookbook_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/cookbooks'\n"
|
||||||
|
"role_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/roles'\n"
|
||||||
|
"environment_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/kitchen/environments'\n"
|
||||||
|
"environment 'production'\n"
|
||||||
|
"node_path '/var/lib/heat-config/"
|
||||||
|
"heat-config-chef/node'")
|
||||||
|
mfdopen.return_value.write.assert_any_call(exp_cfg)
|
||||||
|
mfdopen.return_value.write.assert_any_call(exp_node)
|
Loading…
Reference in New Issue
Block a user