Skip to content

Commit

Permalink
Merge pull request #106 from jordane/override_chef_solo_directory
Browse files Browse the repository at this point in the history
allow overriding chef-solo directory
Fixes #83
  • Loading branch information
tobami committed Oct 12, 2012
2 parents 9ad9aba + a5f390f commit 47d0d9d
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 45 deletions.
18 changes: 14 additions & 4 deletions README.md
Expand Up @@ -15,7 +15,7 @@ It also adds features to Chef Solo that are currently only available for Chef Se

It all starts in the **kitchen**, which you should keep under version control:

* `auth.cfg`: Authentication information needed to be able to connect to the nodes
* `config.cfg`: Configuration, including authentication and run-time options.
* `nodes/`: After recipes are run on [Nodes][], their configuration is stored here in
JSON format. You can manually edit them or even add new ones. The name of a node
should be its FQDN
Expand Down Expand Up @@ -117,17 +117,17 @@ Careful what you do with your nodes!:

### Local Setup

`fix new_kitchen` will create inside the current directory a few files and directories for LittleChef to be able to cook: `auth.cfg`, `roles/`, `data_bags/`, `nodes/`, `cookbooks/` and `site-cookbooks/`. You can create and have as many kitchens as you like on your computer.
`fix new_kitchen` will create inside the current directory a few files and directories for LittleChef to be able to cook: `config.cfg`, `roles/`, `data_bags/`, `nodes/`, `cookbooks/` and `site-cookbooks/`. You can create and have as many kitchens as you like on your computer.

### Authentication

To be able to issue commands to remote nodes, you need to enter a user and a password with sudo rights. `new_kitchen` will have created a file named `auth.cfg`. You can edit it now to enter needed authentication data. There are several possibilities:
To be able to issue commands to remote nodes, you need to enter a user and a password with sudo rights. `new_kitchen` will have created a file named `config.cfg`. You can edit it now to enter needed authentication data. There are several possibilities:

* username and password
* username, password and keypair-file
* A reference to an ssh-config file

The last one allows the most flexibility, as it allows you to define different usernames, passwords and/or keypair-files per hostname. LittleChef will look at `~/.ssh/config` by default, but you can always specify another path in `auth.cfg`:
The last one allows the most flexibility, as it allows you to define different usernames, passwords and/or keypair-files per hostname. LittleChef will look at `~/.ssh/config` by default, but you can always specify another path in `config.cfg`:

```ini
[userinfo]
Expand All @@ -146,6 +146,16 @@ An example `~/.ssh/config` file:
IdentityFile ~/.ssh/dev_rsa
User devuser

### Other Configuration Options

You can also optionally override the directory being used on the nodes to sync your
kitchen to:

```ini
[kitchen]
node_work_path = /tmp/chef-solo
```

### Deploying

For convenience, there is a command that allows you to deploy chef-solo
Expand Down
8 changes: 4 additions & 4 deletions littlechef/chef.py
Expand Up @@ -27,7 +27,7 @@

from littlechef import lib
from littlechef import solo
from littlechef.settings import node_work_path, cookbook_paths
from littlechef.settings import cookbook_paths
from littlechef import LOGFILE, enable_logs as ENABLE_LOGS


Expand Down Expand Up @@ -121,7 +121,7 @@ def _synchronize_node(configfile, node):
os.remove(configfile)
# Synchronize kitchen
rsync_project(
node_work_path, './cookbooks ./data_bags ./roles ./site-cookbooks',
env.node_work_path, './cookbooks ./data_bags ./roles ./site-cookbooks',
exclude=('*.svn', '.bzr*', '.git*', '.hg*'),
delete=True,
extra_opts="-q",
Expand Down Expand Up @@ -299,7 +299,7 @@ def _remove_local_node_data_bag():

def _remove_remote_node_data_bag():
"""Removes generated 'node' data_bag from the remote node"""
node_data_bag_path = os.path.join(node_work_path, 'data_bags', 'node')
node_data_bag_path = os.path.join(env.node_work_path, 'data_bags', 'node')
if exists(node_data_bag_path):
sudo("rm -rf {0}".format(node_data_bag_path))

Expand All @@ -319,7 +319,7 @@ def _add_search_patch():
"""
# Create extra cookbook dir
lib_path = os.path.join(
node_work_path, cookbook_paths[0], 'chef_solo_search_lib', 'libraries')
env.node_work_path, cookbook_paths[0], 'chef_solo_search_lib', 'libraries')
with hide('running', 'stdout'):
sudo('mkdir -p {0}'.format(lib_path))
# Add search and environment patch to the node's cookbooks
Expand Down
52 changes: 31 additions & 21 deletions littlechef/runner.py
Expand Up @@ -26,7 +26,7 @@
from littlechef import solo
from littlechef import lib
from littlechef import chef
from littlechef.settings import cookbook_paths
from littlechef.settings import CONFIGFILE, cookbook_paths, node_work_path


# Fabric settings
Expand Down Expand Up @@ -55,15 +55,17 @@ def _mkdir(d):
_mkdir("data_bags")
for cookbook_path in cookbook_paths:
_mkdir(cookbook_path)
# Add skeleton auth.cfg
if not os.path.exists("auth.cfg"):
with open("auth.cfg", "w") as authfh:
print >> authfh, "[userinfo]"
print >> authfh, "user = "
print >> authfh, "password = "
print >> authfh, "keypair-file = "
print >> authfh, "ssh-config = "
print "auth.cfg file created..."
# Add skeleton config.cfg
if not os.path.exists("config.cfg"):
with open("config.cfg", "w") as configfh:
print >> configfh, "[userinfo]"
print >> configfh, "user = "
print >> configfh, "password = "
print >> configfh, "keypair-file = "
print >> configfh, "ssh-config = "
print >> configfh, "[kitchen]"
print >> configfh, "node_work_path = /tmp/chef-solo/"
print "config.cfg file created..."


@hosts('setup')
Expand Down Expand Up @@ -298,24 +300,25 @@ def list_plugins():
lib.print_plugin_list()


# Check that user is cooking inside a kitchen and configure authentication #
def _check_appliances():
"""Look around and return True or False based on whether we are in a
kitchen
"""
names = os.listdir(os.getcwd())
filenames = os.listdir(os.getcwd())
missing = []
for dirname in ['nodes', 'roles', 'cookbooks', 'data_bags']:
if (dirname not in names) or (not os.path.isdir(dirname)):
if (dirname not in filenames) or (not os.path.isdir(dirname)):
missing.append(dirname)
if 'auth.cfg' not in names:
missing.append('auth.cfg')
return (not bool(missing)), missing


def _readconfig():
"""Configure environment"""
# Check that all dirs and files are present
config = ConfigParser.SafeConfigParser()
found = config.read([CONFIGFILE, 'auth.cfg'])
if not len(found):
abort('No config.cfg file found in the current directory')

in_a_kitchen, missing = _check_appliances()
missing_str = lambda m: ' and '.join(', '.join(m).rsplit(', ', 1))
if not in_a_kitchen:
Expand All @@ -324,8 +327,6 @@ def _readconfig():
"To create a new kitchen in the current directory "\
" type 'fix new_kitchen'"
abort(msg)
config = ConfigParser.ConfigParser()
config.read("auth.cfg")

# We expect an ssh_config file here,
# and/or a user, (password/keyfile) pair
Expand All @@ -334,7 +335,7 @@ def _readconfig():
ssh_config = config.get('userinfo', 'ssh-config')
except ConfigParser.NoSectionError:
msg = 'You need to define a "userinfo" section'
msg += ' in auth.cfg. Refer to the README for help'
msg += ' in config.cfg. Refer to the README for help'
msg += ' (http://github.com/tobami/littlechef)'
abort(msg)
except ConfigParser.NoOptionError:
Expand All @@ -357,7 +358,7 @@ def _readconfig():
except ConfigParser.NoOptionError:
if not ssh_config:
msg = 'You need to define a user in the "userinfo" section'
msg += ' of auth.cfg. Refer to the README for help'
msg += ' of config.cfg. Refer to the README for help'
msg += ' (http://github.com/tobami/littlechef)'
abort(msg)
user_specified = False
Expand All @@ -373,7 +374,16 @@ def _readconfig():
pass

if user_specified and not env.password and not env.ssh_config:
abort('You need to define a password or a ssh-config file in auth.cfg')
abort('You need to define a password or a ssh-config file in config.cfg')

# Node's Chef Solo working directory for storing cookbooks, roles, etc.
try:
env.node_work_path = config.get('kitchen','node_work_path')
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
env.node_work_path = node_work_path
else:
if not env.node_work_path:
abort('The "node_work_path" option cannot be empty')


# Only read config if fix is being used and we are not creating a new kitchen
Expand Down
3 changes: 1 addition & 2 deletions littlechef/settings.py
Expand Up @@ -12,8 +12,7 @@
#See the License for the specific language governing permissions and
#limitations under the License.
#
CONFIGFILE = 'config.cfg'
# Path to look up cookbooks
cookbook_paths = ['site-cookbooks', 'cookbooks']

# Node's Chef Solo working directory for storing cookbooks, roles, etc.
node_work_path = '/tmp/chef-solo'
14 changes: 7 additions & 7 deletions littlechef/solo.py
Expand Up @@ -22,7 +22,7 @@
from fabric.utils import abort

from littlechef.lib import credentials
from littlechef.settings import node_work_path, cookbook_paths
from littlechef.settings import cookbook_paths
from littlechef import LOGFILE as logging_path


Expand Down Expand Up @@ -56,21 +56,21 @@ def configure(current_node=None):
current_node = current_node or {}
with credentials():
# Ensure that the /tmp/chef-solo/cache directory exist
cache_dir = "{0}/cache".format(node_work_path)
cache_dir = "{0}/cache".format(env.node_work_path)
if not exists(cache_dir):
with settings(hide('running', 'stdout'), warn_only=True):
output = sudo('mkdir -p {0}'.format(cache_dir))
if output.failed:
error = "Could not create {0} dir. ".format(node_work_path)
error = "Could not create {0} dir. ".format(env.node_work_path)
error += "Do you have sudo rights?"
abort(error)
# Change ownership of /tmp/chef-solo/ so that we can rsync
with hide('running', 'stdout'):
with settings(warn_only=True):
output = sudo(
'chown -R {0} {1}'.format(env.user, node_work_path))
'chown -R {0} {1}'.format(env.user, env.node_work_path))
if output.failed:
error = "Could not modify {0} dir. ".format(node_work_path)
error = "Could not modify {0} dir. ".format(env.node_work_path)
error += "Do you have sudo rights?"
abort(error)
# Set up chef solo configuration
Expand All @@ -82,10 +82,10 @@ def configure(current_node=None):
reversed_cookbook_paths = cookbook_paths[:]
reversed_cookbook_paths.reverse()
cookbook_paths_list = '[{0}]'.format(', '.join(
['"{0}/{1}"'.format(node_work_path, x) \
['"{0}/{1}"'.format(env.node_work_path, x) \
for x in reversed_cookbook_paths]))
data = {
'node_work_path': node_work_path,
'node_work_path': env.node_work_path,
'cookbook_paths_list': cookbook_paths_list,
'environment': current_node.get('chef_environment', '_default'),
'verbose': "true" if env.verbose else "false"
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/test_command.py
Expand Up @@ -64,7 +64,7 @@ def test_not_a_kitchen(self):
# Call fix from the current directory above "tests/"
resp, error = self.execute([fix, '-l'])
self.assertTrue("Fatal error" in error, resp)
self.assertTrue("outside of a kitchen" in error, error)
self.assertTrue('No config.cfg file found' in error, error)
self.assertEquals(resp, "", resp)
# Return to test dir
self.set_location()
Expand Down
28 changes: 22 additions & 6 deletions tests/test_lib.py
Expand Up @@ -14,9 +14,11 @@
import unittest
import os
import json
from ConfigParser import SafeConfigParser

from mock import patch
from fabric.api import env
from mock import patch

import sys
env_path = "/".join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1])
Expand All @@ -25,7 +27,6 @@
from littlechef import runner, chef, lib


runner.__testing__ = True
littlechef_src = os.path.split(os.path.normpath(os.path.abspath(__file__)))[0]
littlechef_top = os.path.normpath(os.path.join(littlechef_src, '..'))

Expand All @@ -39,6 +40,7 @@ def setUp(self):
'testnode3.mydomain.com',
'testnode4'
]
runner.__testing__ = True

def tearDown(self):
for nodename in self.nodes + ["extranode"]:
Expand All @@ -51,15 +53,27 @@ def tearDown(self):
runner.env.chef_environment = None
runner.env.hosts = []
runner.env.all_hosts = []
runner.env.ssh_config = None
runner.env.key_filename = None
runner.env.node_work_path = None


class TestRunner(BaseTest):
def test_get_config(self):
"""Should read configuration from config file when config.cfg is found
"""
runner._readconfig()
self.assertEqual(runner.env.ssh_config, None)
self.assertEqual(runner.env.user, "testuser")
self.assertEqual(runner.env.password, "testpass")
self.assertEqual(runner.env.key_filename, None)
self.assertEqual(runner.env.node_work_path, "/tmp/chef-solo")

def test_not_a_kitchen(self):
"""Should exit with error when not a kitchen directory"""
# Change to a directory which is not a kitchen
# NOTE: We need absolute paths for the kitchen
os.chdir(littlechef_top)
self.assertRaises(SystemExit, runner._readconfig)
"""Should abort when no config file found"""
with patch.object(SafeConfigParser, 'read') as mock_method:
mock_method.return_value = []
self.assertRaises(SystemExit, runner._readconfig)

def test_nodes_with_role(self):
"""Should return a list of nodes with the given role in the run_list"""
Expand All @@ -73,6 +87,8 @@ def test_nodes_with_role_in_env(self):
self.assertEqual(runner.env.hosts, ['testnode2'])

def test_nodes_with_role_in_env_empty(self):
"""Should abort when no nodes with given role found in the environment
"""
runner.env.chef_environment = "production"
self.assertRaises(
SystemExit, runner.nodes_with_role, "all_you_can_eat")
Expand Down

0 comments on commit 47d0d9d

Please sign in to comment.