Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

list and deploy nodes with a certain tag #121

Closed
wants to merge 7 commits into from

2 participants

@thekorn
Collaborator

With this branch two new commands are added:

LittleChef: Configuration Management using Chef Solo

Available commands:

[...]
    list_nodes_with_tag     Show all nodes which have assigned a given tag
    nodes_with_tag          Sets a list of nodes that have the given tag assigned and calls node()
[...]

The idea is to be able list and deploy nodes with a certain tag, similar to how the nodes_with_role selector is working.

Sample calls would be like

% fix list_nodes_with_tag:boo
[...]
% fix nodes_with_tag:baz
[...]

Also an additional commandline switch called --include-guests is introduced. This switch is useful if you have the convention that all tags for the host are virtually also tags for the host's guests. In this case fix is not only applied on the host with a tag, but also on all of the host's guests.

I don't consider this branch done yet, but would like to get some input on the general concept first (esp. from @tobami). Missing bits are:

  • documentation in README
  • missing tests for the new functionallity (blocked by already failing tests in master #120)

Any initial comments?

@tobami
Owner

Looks awesome. The "include-guests" bit is kind of a new concept in LittleChef, but because it is opt-in it is alright.

Only the fix for the command tests don't look right. They fail depending on your DNS. What is needed is mocking or localhost DNS settings for tests

@tobami
Owner

Only the README docu entry is missing

@tobami
Owner

cherry-picking commits and creating new pr

@tobami tobami closed this
@tobami tobami deleted the nodes_with_tags branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
8 fix
@@ -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() ##
@@ -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
View
1  littlechef/__init__.py
@@ -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']
View
18 littlechef/lib.py
@@ -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
View
26 littlechef/runner.py
@@ -90,6 +90,17 @@ def nodes_with_role(rolename):
@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"""
if not len(nodes) or nodes[0] == '':
@@ -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:
@@ -251,17 +263,23 @@ 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"""
for recipe in lib.get_recipes():
@@ -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
View
12 tests/test_command.py
@@ -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"""
@@ -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"""
@@ -144,20 +144,20 @@ 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"""
@@ -165,7 +165,7 @@ def test_ssh(self):
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):
Something went wrong with that request. Please try again.