Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

list and deploy nodes with a certain tag #121

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions fix
Expand Up @@ -66,6 +66,11 @@ parser.add_option(
"--env", dest="environment", default=None,
help="Using a certain chef environment"
)
parser.add_option(
"--include-guests", dest="include_guests",
action="store_true", default=False,
help="when searching for nodes with tags also include guests"
)
(options, args) = parser.parse_args()

## Process args list and call fabric's main() ##
Expand Down Expand Up @@ -98,6 +103,9 @@ else:
littlechef.loglevel = 'debug'
littlechef.verbose = True
sys.argv.remove('--debug')
if options.include_guests:
littlechef.include_guests = True
sys.argv.remove('--include-guests')
if options.environment is not None:
# Check for mistakes:
# * fix --env list_nodes
Expand Down
1 change: 1 addition & 0 deletions littlechef/__init__.py
Expand Up @@ -26,6 +26,7 @@
enable_logs = True
LOGFILE = '/var/log/chef/solo.log'
whyrun = False
include_guests = False

node_work_path = '/tmp/chef-solo'
cookbook_paths = ['site-cookbooks', 'cookbooks']
Expand Down
18 changes: 18 additions & 0 deletions littlechef/lib.py
Expand Up @@ -79,6 +79,24 @@ def get_nodes_with_role(role_name, environment=None):
yield n


def get_nodes_with_tag(tag, environment=None, include_guests=False):
"""Get all nodes which include a given tag"""
nodes = get_nodes(environment)
nodes_mapping = dict((n['name'], n) for n in nodes)
for n in nodes:
if tag in n.get('tags', []) and \
n.get('virtualization', {}).get('role') == 'host':
yield n
if include_guests:
for guest in n['virtualization']['guests']:
try:
yield nodes_mapping[guest['fqdn']]
except KeyError:
# we ignore guests which are not in the same
# chef environments than their hosts for now
pass


def get_nodes_with_recipe(recipe_name, environment=None):
"""Get all nodes which include a given recipe,
prefix-searches are also supported
Expand Down
26 changes: 23 additions & 3 deletions littlechef/runner.py
Expand Up @@ -89,6 +89,17 @@ def nodes_with_role(rolename):
return node(*nodes_in_env)


@hosts('setup')
def nodes_with_tag(tag):
"""Sets a list of nodes that have the given tag assigned and calls node()"""
nodes = lib.get_nodes_with_tag(tag, env.chef_environment, env.include_guests)
nodes = [n['name'] for n in nodes]
if not len(nodes):
print("No nodes found with tag '{0}'".format(tag))
sys.exit(0)
return node(*nodes)


@hosts('setup')
def node(*nodes):
"""Selects and configures a list of nodes. 'all' configures all nodes"""
Expand Down Expand Up @@ -119,7 +130,8 @@ def node(*nodes):
execute = True
if not(littlechef.__cooking__ and
'node:' not in sys.argv[-1] and
'nodes_with_role:' not in sys.argv[-1]):
'nodes_with_role:' not in sys.argv[-1] and
'nodes_with_tag:' not in sys.argv[-1]):
# If user didn't type recipe:X, role:Y or deploy_chef,
# configure the nodes
for hostname in env.hosts:
Expand Down Expand Up @@ -251,16 +263,22 @@ def list_nodes_detailed():

@hosts('api')
def list_nodes_with_recipe(recipe):
"""Show all nodes which have asigned a given recipe"""
"""Show all nodes which have assigned a given recipe"""
lib.print_nodes(lib.get_nodes_with_recipe(recipe, env.chef_environment))


@hosts('api')
def list_nodes_with_role(role):
"""Show all nodes which have asigned a given role"""
"""Show all nodes which have assigned a given role"""
lib.print_nodes(lib.get_nodes_with_role(role, env.chef_environment))


@hosts('api')
def list_nodes_with_tag(tag):
"""Show all nodes which have assigned a given tag"""
lib.print_nodes(lib.get_nodes_with_tag(tag, env.chef_environment, env.include_guests))


@hosts('api')
def list_recipes():
"""Show a list of all available recipes"""
Expand Down Expand Up @@ -401,6 +419,8 @@ def _readconfig():
env.loglevel = littlechef.loglevel
env.verbose = littlechef.verbose
env.node_work_path = littlechef.node_work_path
env.include_guests = littlechef.include_guests
env.follow_symlinks = False

if littlechef.__cooking__:
# Called from command line
Expand Down
12 changes: 6 additions & 6 deletions tests/test_command.py
Expand Up @@ -82,7 +82,7 @@ def test_list_commands(self):
self.assertEquals(error, "")
expected = "LittleChef: Configuration Management using Chef Solo"
self.assertTrue(expected in resp)
self.assertEquals(len(resp.split('\n')), 22)
self.assertEquals(len(resp.split('\n')), 24)

#def test_verbose(self):
#"""Should turn on verbose output"""
Expand Down Expand Up @@ -132,7 +132,7 @@ def test_one_node(self):
resp, error = self.execute([fix, 'node:testnode2'])
self.assertTrue("== Configuring testnode2 ==" in resp)
# Will try to configure testnode2 and will fail DNS lookup
self.assertTrue("tal error: Name lookup failed for testnode2" in error,
self.assertTrue("Fatal error: Timed out trying to connect to testnode2" in error,
error)
#def test_dummy_node(self): # FIXME: Needs mocking
"""Should *not* configure a node when dummy is set to true"""
Expand All @@ -144,28 +144,28 @@ def test_several_nodes(self):
resp, error = self.execute([fix, 'node:testnode2,testnode1'])
self.assertTrue("== Configuring testnode2 ==" in resp)
# Will try to configure *first* testnode2 and will fail DNS lookup
self.assertTrue("tal error: Name lookup failed for testnode2" in error)
self.assertTrue("Fatal error: Timed out trying to connect to testnode2" in error)

def test_recipe(self):
"""Should configure node with the given recipe"""
resp, error = self.execute(
[fix, 'node:testnode1', 'recipe:subversion'])
self.assertTrue("plying recipe 'subversion' on node testnode1" in resp)
self.assertTrue("tal error: Name lookup failed for testnode1" in error)
self.assertTrue("Fatal error: Timed out trying to connect to testnode1" in error)

def test_role(self):
"""Should configure node with the given role"""
resp, error = self.execute([fix, 'node:testnode1', 'role:base'])
self.assertTrue("== Applying role 'base' to testnode1 ==" in resp)
self.assertTrue("tal error: Name lookup failed for testnode1" in error)
self.assertTrue("Fatal error: Timed out trying to connect to testnode1" in error)

def test_ssh(self):
"""Should execute the given ssh command"""
resp, error = self.execute([fix, 'node:testnode2', 'ssh:"my command"'])
expected = "Executing the command '\"my command\"' on the node"
expected += " testnode2..."
self.assertTrue(expected in resp)
expected = "tal error: Name lookup failed for testnode2"
expected = "Fatal error: Timed out trying to connect to testnode2"
self.assertTrue(expected in error, error)

def test_plugin(self):
Expand Down