[Python-apps-commits] r14175 - in packages/bundlewrap/trunk (26 files)
highvoltage-guest at users.alioth.debian.org
highvoltage-guest at users.alioth.debian.org
Thu Jul 6 18:52:09 UTC 2017
Date: Thursday, July 6, 2017 @ 18:52:08
Author: highvoltage-guest
Revision: 14175
New upstream version 2.9.0
Modified:
packages/bundlewrap/trunk/CHANGELOG.md
packages/bundlewrap/trunk/bundlewrap/__init__.py
packages/bundlewrap/trunk/bundlewrap/cmdline/apply.py
packages/bundlewrap/trunk/bundlewrap/cmdline/lock.py
packages/bundlewrap/trunk/bundlewrap/cmdline/metadata.py
packages/bundlewrap/trunk/bundlewrap/cmdline/parser.py
packages/bundlewrap/trunk/bundlewrap/cmdline/run.py
packages/bundlewrap/trunk/bundlewrap/concurrency.py
packages/bundlewrap/trunk/bundlewrap/deps.py
packages/bundlewrap/trunk/bundlewrap/items/actions.py
packages/bundlewrap/trunk/bundlewrap/lock.py
packages/bundlewrap/trunk/bundlewrap/node.py
packages/bundlewrap/trunk/bundlewrap/operations.py
packages/bundlewrap/trunk/bundlewrap/repo.py
packages/bundlewrap/trunk/bundlewrap/utils/__init__.py
packages/bundlewrap/trunk/debian/bw.1
packages/bundlewrap/trunk/debian/changelog
packages/bundlewrap/trunk/debian/copyright
packages/bundlewrap/trunk/docs/content/guide/cli.md
packages/bundlewrap/trunk/docs/content/guide/quickstart.md
packages/bundlewrap/trunk/docs/content/items/action.md
packages/bundlewrap/trunk/docs/content/repo/hooks.md
packages/bundlewrap/trunk/docs/content/repo/nodes.py.md
packages/bundlewrap/trunk/setup.py
packages/bundlewrap/trunk/tests/integration/bw_apply_actions.py
packages/bundlewrap/trunk/tests/integration/bw_metadata.py
Modified: packages/bundlewrap/trunk/CHANGELOG.md
===================================================================
--- packages/bundlewrap/trunk/CHANGELOG.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/CHANGELOG.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,3 +1,14 @@
+# 2.19.0
+
+2017-07-05
+
+* actions can now receive data over stdin
+* added `Node.magic_number`
+* added `bw apply --resume-file`
+* added hooks for `bw lock`
+* added `bw metadata --table`
+
+
# 2.18.1
2017-06-01
Modified: packages/bundlewrap/trunk/bundlewrap/__init__.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/__init__.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/__init__.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
-VERSION = (2, 18, 1)
+VERSION = (2, 19, 0)
VERSION_STRING = ".".join([str(v) for v in VERSION])
Modified: packages/bundlewrap/trunk/bundlewrap/cmdline/apply.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/cmdline/apply.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/cmdline/apply.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -6,6 +6,7 @@
from ..concurrency import WorkerPool
from ..exceptions import ItemDependencyLoop
+from ..utils import SkipList
from ..utils.cmdline import get_target_nodes
from ..utils.plot import explain_item_dependency_loop
from ..utils.table import ROW_SEPARATOR, render_table
@@ -39,6 +40,7 @@
start_time = datetime.now()
results = []
+ skip_list = SkipList(args['resume_file'])
def tasks_available():
return bool(pending_nodes)
@@ -52,14 +54,16 @@
'autoskip_selector': args['autoskip'],
'force': args['force'],
'interactive': args['interactive'],
+ 'skip_list': skip_list,
'workers': args['item_workers'],
'profiling': args['profiling'],
},
}
def handle_result(task_id, return_value, duration):
- if return_value is None: # node skipped because it had no items
+ if return_value is None: # node skipped
return
+ skip_list.add(task_id)
results.append(return_value)
if args['profiling']:
total_time = 0.0
@@ -95,6 +99,7 @@
next_task,
handle_result=handle_result,
handle_exception=handle_exception,
+ cleanup=skip_list.dump,
pool_id="apply",
workers=args['node_workers'],
)
Modified: packages/bundlewrap/trunk/bundlewrap/cmdline/lock.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/cmdline/lock.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/cmdline/lock.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -19,7 +19,7 @@
if list(node.items):
_targets.append(node)
else:
- io.stdout(_("{x} {node} has no items").format(node=bold(node.name), x=yellow("!")))
+ io.stdout(_("{x} {node} has no items").format(node=bold(node.name), x=yellow("»")))
return _targets
@@ -157,6 +157,7 @@
def handle_result(task_id, return_value, duration):
locks_on_node[task_id] = return_value
+ repo.hooks.lock_show(repo, repo.get_node(task_id), return_value)
def handle_exception(task_id, exception, traceback):
msg = "{}: {}".format(task_id, exception)
Modified: packages/bundlewrap/trunk/bundlewrap/cmdline/metadata.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/cmdline/metadata.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/cmdline/metadata.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,20 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+from decimal import Decimal
from json import dumps
from ..metadata import MetadataJSONEncoder, value_at_key_path
-from ..utils.cmdline import get_node
-from ..utils.text import force_text
-from ..utils.ui import io
+from ..utils import Fault
+from ..utils.cmdline import get_node, get_target_nodes
+from ..utils.table import ROW_SEPARATOR, render_table
+from ..utils.text import bold, force_text, mark_for_translation as _, red
+from ..utils.ui import io, page_lines
def bw_metadata(repo, args):
- node = get_node(repo, args['node'], adhoc_nodes=args['adhoc_nodes'])
- for line in dumps(
- value_at_key_path(node.metadata, args['keys']),
- cls=MetadataJSONEncoder,
- indent=4,
- sort_keys=True,
- ).splitlines():
- io.stdout(force_text(line))
+ if args['table']:
+ if not args['keys']:
+ io.stdout(_("{x} at least one key is required with --table").format(x=red("!!!")))
+ exit(1)
+ target_nodes = get_target_nodes(repo, args['target'], adhoc_nodes=args['adhoc_nodes'])
+ key_paths = [path.strip().split(" ") for path in " ".join(args['keys']).split(",")]
+ table = [[bold(_("node"))] + [bold(" ".join(path)) for path in key_paths], ROW_SEPARATOR]
+ for node in target_nodes:
+ values = []
+ for key_path in key_paths:
+ try:
+ value = value_at_key_path(node.metadata, key_path)
+ except KeyError:
+ value = red(_("<missing>"))
+ if isinstance(value, (dict, list, tuple)):
+ value = ", ".join([str(item) for item in value])
+ elif isinstance(value, set):
+ value = ", ".join(sorted(value))
+ elif isinstance(value, (bool, float, int, Decimal, Fault)):
+ value = str(value)
+ values.append(value)
+ table.append([bold(node.name)] + values)
+ page_lines(render_table(table))
+ else:
+ node = get_node(repo, args['target'], adhoc_nodes=args['adhoc_nodes'])
+ for line in dumps(
+ value_at_key_path(node.metadata, args['keys']),
+ cls=MetadataJSONEncoder,
+ indent=4,
+ sort_keys=True,
+ ).splitlines():
+ io.stdout(force_text(line))
Modified: packages/bundlewrap/trunk/bundlewrap/cmdline/parser.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/cmdline/parser.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/cmdline/parser.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -151,6 +151,18 @@
dest='summary',
help=_("don't show stats summary"),
)
+ parser_apply.add_argument(
+ "-r",
+ "--resume-file",
+ default=None,
+ dest='resume_file',
+ help=_(
+ "path to a file that a list of completed nodes will be added to; "
+ "if the file already exists, any nodes therein will be skipped"
+ ),
+ metavar=_("PATH"),
+ type=str,
+ )
# bw debug
help_debug = _("Start an interactive Python shell for this repository")
@@ -410,7 +422,7 @@
)
parser_metadata.set_defaults(func=bw_metadata)
parser_metadata.add_argument(
- 'node',
+ 'target',
metavar=_("NODE"),
type=str,
help=_("node to print JSON-formatted metadata for"),
@@ -423,6 +435,17 @@
type=str,
help=_("print only partial metadata from the given space-separated key path"),
)
+ parser_metadata.add_argument(
+ "-t",
+ "--table",
+ action='store_true',
+ dest='table',
+ help=_(
+ "show a table of selected metadata values from multiple nodes instead; "
+ "allows for multiple comma-separated paths in KEY; "
+ "allows for node selectors in NODE (e.g. 'NODE1,NODE2,GROUP1,bundle:BUNDLE1...')"
+ ),
+ )
# bw nodes
help_nodes = _("List all nodes in this repository")
@@ -724,6 +747,18 @@
"(defaults to {})").format(bw_run_p_default),
type=int,
)
+ parser_run.add_argument(
+ "-r",
+ "--resume-file",
+ default=None,
+ dest='resume_file',
+ help=_(
+ "path to a file that a list of completed nodes will be added to; "
+ "if the file already exists, any nodes therein will be skipped"
+ ),
+ metavar=_("PATH"),
+ type=str,
+ )
# bw stats
help_stats = _("Show some statistics about your repository")
Modified: packages/bundlewrap/trunk/bundlewrap/cmdline/run.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/cmdline/run.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/cmdline/run.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -5,6 +5,7 @@
from ..concurrency import WorkerPool
from ..exceptions import NodeLockedException
+from ..utils import SkipList
from ..utils.cmdline import get_target_nodes
from ..utils.text import mark_for_translation as _
from ..utils.text import bold, error_summary, green, red, yellow
@@ -12,11 +13,15 @@
from ..utils.ui import io
-def run_on_node(node, command, may_fail, ignore_locks, log_output):
+def run_on_node(node, command, may_fail, ignore_locks, log_output, skip_list):
if node.dummy:
- io.stdout(_("{x} {node} is a dummy node").format(node=bold(node.name), x=yellow("!")))
- return
+ io.stdout(_("{x} {node} is a dummy node").format(node=bold(node.name), x=yellow("»")))
+ return None
+ if node.name in skip_list:
+ io.stdout(_("{x} {node} skipped by --resume-file").format(node=bold(node.name), x=yellow("»")))
+ return None
+
node.repo.hooks.node_run_start(
node.repo,
node,
@@ -59,6 +64,7 @@
node=bold(node.name),
x=red("â"),
))
+ return result.return_code
def bw_run(repo, args):
@@ -74,6 +80,8 @@
)
start_time = datetime.now()
+ skip_list = SkipList(args['resume_file'])
+
def tasks_available():
return bool(pending_nodes)
@@ -88,9 +96,14 @@
args['may_fail'],
args['ignore_locks'],
True,
+ skip_list,
),
}
+ def handle_result(task_id, return_value, duration):
+ if return_value == 0:
+ skip_list.add(task_id)
+
def handle_exception(task_id, exception, traceback):
if isinstance(exception, NodeLockedException):
msg = _(
@@ -111,7 +124,9 @@
worker_pool = WorkerPool(
tasks_available,
next_task,
+ handle_result=handle_result,
handle_exception=handle_exception,
+ cleanup=skip_list.dump,
pool_id="run",
workers=args['node_workers'],
)
Modified: packages/bundlewrap/trunk/bundlewrap/concurrency.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/concurrency.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/concurrency.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -23,6 +23,7 @@
next_task,
handle_result=None,
handle_exception=None,
+ cleanup=None,
pool_id=None,
workers=4,
):
@@ -33,6 +34,7 @@
self.next_task = next_task
self.handle_result = handle_result
self.handle_exception = handle_exception
+ self.cleanup = cleanup
self.number_of_workers = workers
self.idle_workers = set(range(self.number_of_workers))
@@ -169,6 +171,8 @@
return processed_results
finally:
io.debug(_("shutting down worker pool {pool}").format(pool=self.pool_id))
+ if self.cleanup:
+ self.cleanup()
self.executor.shutdown()
io.debug(_("worker pool {pool} has been shut down").format(pool=self.pool_id))
Modified: packages/bundlewrap/trunk/bundlewrap/deps.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/deps.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/deps.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -212,7 +212,7 @@
try:
target_item = items[target_item_id]
- except NoSuchItem:
+ except KeyError:
raise BundleError(_(
"{item} in bundle '{bundle}' triggers unknown item '{target_item}'"
).format(
Modified: packages/bundlewrap/trunk/bundlewrap/items/actions.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/items/actions.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/items/actions.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -5,6 +5,7 @@
from bundlewrap.exceptions import ActionFailure, BundleError
from bundlewrap.items import format_comment, Item
+from bundlewrap.utils import Fault
from bundlewrap.utils.ui import io
from bundlewrap.utils.text import mark_for_translation as _
from bundlewrap.utils.text import blue, bold, wrap_question
@@ -17,6 +18,7 @@
BUNDLE_ATTRIBUTE_NAME = 'actions'
ITEM_ATTRIBUTES = {
'command': None,
+ 'data_stdin': None,
'expected_stderr': None,
'expected_stdout': None,
'expected_return_code': 0,
@@ -68,7 +70,10 @@
))
return (self.STATUS_SKIPPED, ["unless"])
- question_body = self.attributes['command']
+ question_body = ""
+ if self.attributes['data_stdin'] is not None:
+ question_body += "<" + _("data") + "> | "
+ question_body += self.attributes['command']
if self.comment:
question_body += format_comment(self.comment)
@@ -128,6 +133,17 @@
return status_code
def run(self):
+ if self.attributes['data_stdin'] is not None:
+ data_stdin = self.attributes['data_stdin']
+ # Allow users to use either a string/unicode object or raw
+ # bytes -- or Faults.
+ if isinstance(data_stdin, Fault):
+ data_stdin = data_stdin.value
+ if type(data_stdin) is not bytes:
+ data_stdin = data_stdin.encode('UTF-8')
+ else:
+ data_stdin = None
+
with io.job(_(" {node} {bundle} {item} running...").format(
bundle=self.bundle.name,
item=self.id,
@@ -135,6 +151,7 @@
)):
result = self.bundle.node.run(
self.attributes['command'],
+ data_stdin=data_stdin,
may_fail=True,
)
Modified: packages/bundlewrap/trunk/bundlewrap/lock.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/lock.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/lock.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -162,6 +162,8 @@
node.run("mkdir -p " + quote(SOFT_LOCK_PATH))
node.upload(local_path, SOFT_LOCK_FILE.format(id=lock_id), mode='0644')
+ node.repo.hooks.lock_add(node.repo, node, lock_id, item_selectors, expiry_timestamp, comment)
+
return lock_id
@@ -199,3 +201,4 @@
node=node.name,
))
node.run("rm {}".format(SOFT_LOCK_FILE.format(id=lock_id)))
+ node.repo.hooks.lock_remove(node.repo, node, lock_id)
Modified: packages/bundlewrap/trunk/bundlewrap/node.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/node.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/node.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -2,6 +2,7 @@
from __future__ import unicode_literals
from datetime import datetime, timedelta
+from hashlib import md5
from os import environ
from sys import exit
from threading import Lock
@@ -526,6 +527,10 @@
for item in bundle.items:
yield item
+ @cached_property
+ def magic_number(self):
+ return int(md5(self.name.encode('UTF-8')).hexdigest(), 16)
+
@property
def _static_items(self):
for bundle in self.bundles:
@@ -537,17 +542,22 @@
autoskip_selector="",
interactive=False,
force=False,
+ skip_list=tuple(),
workers=4,
profiling=False,
):
if not list(self.items):
- io.stdout(_("{x} {node} has no items").format(node=bold(self.name), x=yellow("!")))
+ io.stdout(_("{x} {node} has no items").format(node=bold(self.name), x=yellow("»")))
return None
if self.covered_by_autoskip_selector(autoskip_selector):
- io.debug(_("skipping {}, matches autoskip selector").format(self.name))
+ io.stdout(_("{x} {node} skipped by --skip").format(node=bold(self.name), x=yellow("»")))
return None
+ if self.name in skip_list:
+ io.stdout(_("{x} {node} skipped by --resume-file").format(node=bold(self.name), x=yellow("»")))
+ return None
+
start = datetime.now()
io.stdout(_("{x} {node} {started} at {time}").format(
@@ -660,7 +670,7 @@
"""
return self.repo._metadata_for_node(self.name, partial=True)
- def run(self, command, may_fail=False, log_output=False):
+ def run(self, command, data_stdin=None, may_fail=False, log_output=False):
if log_output:
def log_function(msg):
io.stdout("{x} {node} {msg}".format(
@@ -696,6 +706,7 @@
self.hostname,
command,
add_host_keys=add_host_keys,
+ data_stdin=data_stdin,
ignore_failure=may_fail,
log_function=log_function,
wrapper_inner=self.cmd_wrapper_inner,
Modified: packages/bundlewrap/trunk/bundlewrap/operations.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/operations.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/operations.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -90,6 +90,7 @@
hostname,
command,
add_host_keys=False,
+ data_stdin=None,
ignore_failure=False,
log_function=None,
wrapper_inner="{}",
@@ -138,6 +139,9 @@
)
io._ssh_pids.append(ssh_process.pid)
+ if data_stdin is not None:
+ ssh_process.stdin.write(data_stdin)
+
quit_event = Event()
stdout_thread = Thread(
args=(stdout_lb, stdout_fd_r, quit_event, True),
Modified: packages/bundlewrap/trunk/bundlewrap/repo.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/repo.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/repo.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -39,18 +39,21 @@
FILENAME_REQUIREMENTS = "requirements.txt"
HOOK_EVENTS = (
+ 'action_run_end',
'action_run_start',
- 'action_run_end',
+ 'apply_end',
'apply_start',
- 'apply_end',
+ 'item_apply_end',
'item_apply_start',
- 'item_apply_end',
+ 'lock_add',
+ 'lock_remove',
+ 'lock_show',
+ 'node_apply_end',
'node_apply_start',
- 'node_apply_end',
+ 'node_run_end',
'node_run_start',
- 'node_run_end',
+ 'run_end',
'run_start',
- 'run_end',
'test',
'test_node',
)
Modified: packages/bundlewrap/trunk/bundlewrap/utils/__init__.py
===================================================================
--- packages/bundlewrap/trunk/bundlewrap/utils/__init__.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/bundlewrap/utils/__init__.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -300,6 +300,31 @@
return hasher.hexdigest()
+class SkipList(object):
+ """
+ Used to maintain a list of nodes that have already been visited.
+ """
+ def __init__(self, path):
+ self.path = path
+ if path and exists(path):
+ with open(path) as f:
+ self._list_items = set(f.read().strip().split("\n"))
+ else:
+ self._list_items = set()
+
+ def __contains__(self, item):
+ return item in self._list_items
+
+ def add(self, item):
+ if self.path:
+ self._list_items.add(item)
+
+ def dump(self):
+ if self.path:
+ with open(self.path, 'w') as f:
+ f.write("\n".join(sorted(self._list_items)) + "\n")
+
+
@contextmanager
def tempfile():
handle, path = mkstemp()
Modified: packages/bundlewrap/trunk/debian/bw.1
===================================================================
--- packages/bundlewrap/trunk/debian/bw.1 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/debian/bw.1 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,30 +1,16 @@
-.\" Hey, EMACS: -*- nroff -*-
.\" (C) Copyright 2016 Jonathan Carter <jcarter at linux.com>,
-.\"
-.\" First parameter, NAME, should be all caps
-.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
-.\" other parameters are allowed: see man(7), man(1)
+
.TH Bundlewrap 1 "September 16 2016"
-.\" Please adjust this date whenever revising the manpage.
-.\"
-.\" Some roff macros, for reference:
-.\" .nh disable hyphenation
-.\" .hy enable hyphenation
-.\" .ad l left justify
-.\" .ad b justify to both left and right margins
-.\" .nf disable filling
-.\" .fi enable filling
-.\" .br insert line break
-.\" .sp <n> insert n+1 empty lines
-.\" for manpage-specific macros, see man(7)
+
.SH NAME
bundlewrap \- Decentralized configuration management system with Python
.SH SYNOPSIS
.B bundlewrap
-.RI [ options ] " files" ...
+.RI [-h] [-a] [-A] [-d] [-r DIRECTORY] [--version]
.br
-.B bar
-.RI [ options ] " files" ...
+.B {apply,debug,groups,hash,items,lock,metadata,nodes,plot,repo,run,stats,test,verify,zen}
+.RI ...
+
.SH DESCRIPTION
BundleWrap fills the gap between complex deployments using Chef or
Puppet and old school system administration over SSH. You do not need
@@ -38,30 +24,82 @@
everything is configured the way it's supposed to be. You won't have to
install anything on managed servers.
-.B bundlewrap
-and
-.B bar
-commands.
+.SH OPTIONAL ARGUMENTS
+
.PP
-.\" TeX users may be more comfortable with the \fB<whatever>\fP and
-.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
-.\" respectively.
-\fBbundlewrap\fP is a program that...
-.SH OPTIONS
-These programs follow the usual GNU command line syntax, with long
-options starting with two dashes (`-').
-A summary of options is included below.
-For a complete description, see the Info files.
-.TP
.B \-h, \-\-help
-Show summary of options.
+show this help message and exit
.TP
-.B \-v, \-\-version
-Show version of program.
-.SH SEE ALSO
-.BR bar (1),
-.BR baz (1).
-.br
-The programs are documented fully by
-.IR "The Rise and Fall of a Fooish Bar" ,
-available via the Info system.
+.B \-a, \-\-add-host-keys
+set StrictHostKeyChecking=no instead of yes for SSH
+.TP
+.B \-A, \-\-adhoc-nodes
+treat unknown node names as adhoc 'virtual' nodes that
+receive configuration only through groups whose
+member_patterns match the node name given on the
+command line (which also has to be a resolvable
+hostname)
+.TP
+.B \-d, \-\-debug
+print debugging info (implies -v)
+.TP
+.B \-r DIRECTORY, \-\-repo-path DIRECTORY
+Look for repository at this path (defaults to current
+working directory)
+.TP
+.B \-\-version
+show program's version number and exit
+
+.SH SUBCOMMANDS:
+
+.PP
+use 'bw <subcommand> --help' for more info
+.TP
+.B apply
+Applies the configuration defined in your repository to your nodes
+.TP
+.B debug
+Start an interactive Python shell for this repository
+.TP
+.B groups
+Lists groups in this repository (deprecated, use `bw nodes -a`)
+.TP
+.B hash
+Shows a SHA1 hash that summarizes the entire configuration for this repo, node, group, or item.
+.TP
+.B items
+List and preview items for a specific node
+.TP
+.B lock
+Manage locks on nodes used to prevent collisions between BundleWrap users
+.TP
+.B metadata
+View a JSON representation of a node's metadata
+.TP
+.B nodes
+List all nodes in this repository
+.TP
+.B plot
+Generates DOT output that can be piped into `dot -Tsvg -ooutput.svg`. The resulting output.svg can be viewed using most browsers.
+.TP
+.B repo
+Various subcommands to manipulate your repository
+.TP
+.B run
+Run a one-off command on a number of nodes
+.TP
+.B stats
+Show some statistics about your repository
+.TP
+.B test
+Test your repository for consistency (you can use this with a CI tool like Jenkins)
+.TP
+.B verify
+Inspect the health or 'correctness' of a node without changing it
+
+.SH REPORTING BUGS
+Bundlewrap bug tracker: https://github.com/bundlewrap/bundlewrap/issues
+
+.SH AUTHORS
+This manual page was written by Jonathan Carter <jcarter at linux.com>
+Bundlewrap was written by Torsten Rehn <torsten at rehn.email>
Modified: packages/bundlewrap/trunk/debian/changelog
===================================================================
--- packages/bundlewrap/trunk/debian/changelog 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/debian/changelog 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,3 +1,11 @@
+bundlewrap (2.19.0-1) unstable; urgency=medium
+
+ * New upstream release
+ * Update copyright years
+ * Update man page
+
+ -- Jonathan Carter <jcarter at linux.com> Thu, 06 Jul 2017 20:27:00 +0200
+
bundlewrap (2.18.1-2) unstable; urgency=medium
* Update standards-version to 4.0.0
Modified: packages/bundlewrap/trunk/debian/copyright
===================================================================
--- packages/bundlewrap/trunk/debian/copyright 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/debian/copyright 2017-07-06 18:52:08 UTC (rev 14175)
@@ -3,7 +3,7 @@
Source: https://github.com/bundlewrap/bundlewrap
Files: *
-Copyright: Torsten Rehn <torsten at rehn.email>
+Copyright: 2016-2017 Torsten Rehn <torsten at rehn.email>
Comment: Copyrights are assigned to Torsten Rehn (see: CAA.md)
Additional author: Peter Hofmann <scm at uninformativ.de>
Additional author: Tim Buchwaldt <tim at buchwaldt.ws>
@@ -12,7 +12,7 @@
License: GPL-3
Files: debian/*
-Copyright: 2016 Jonathan Carter <jcarter at linux.com>
+Copyright: 2016-2017 Jonathan Carter <jcarter at linux.com>
License: GPL-3
License: GPL-3
Modified: packages/bundlewrap/trunk/docs/content/guide/cli.md
===================================================================
--- packages/bundlewrap/trunk/docs/content/guide/cli.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/docs/content/guide/cli.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -2,7 +2,7 @@
The `bw` utility is BundleWrap's command line interface.
-<div class="alert">This page is not meant as a complete reference. It provides a starting point to explore the various subcommands. If you're looking for details, <code>--help</code> is your friend.</div>
+<div class="alert alert-info">This page is not meant as a complete reference. It provides a starting point to explore the various subcommands. If you're looking for details, <code>--help</code> is your friend.</div>
## bw apply
Modified: packages/bundlewrap/trunk/docs/content/guide/quickstart.md
===================================================================
--- packages/bundlewrap/trunk/docs/content/guide/quickstart.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/docs/content/guide/quickstart.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -30,11 +30,11 @@
The contents should be fairly self-explanatory, but you can always check the [docs](../repo/layout.md) on these files if you want to go deeper.
-<div class="alert">It is highly recommended to use git or a similar tool to keep track of your repository. You may want to start doing that right away.</div>
+<div class="alert alert-info">It is highly recommended to use git or a similar tool to keep track of your repository. You may want to start doing that right away.</div>
At this point you will want to edit `nodes.py` and maybe change "localhost" to the hostname of a system you have passwordless (including sudo) SSH access to.
-<div class="alert">BundleWrap will honor your <code>~/.ssh/config</code>, so if <code>ssh mynode.example.com sudo id</code> works without any password prompts in your terminal, you're good to go.</div>
+<div class="alert alert-info">BundleWrap will honor your <code>~/.ssh/config</code>, so if <code>ssh mynode.example.com sudo id</code> works without any password prompts in your terminal, you're good to go.</div>
Run a command
@@ -44,7 +44,7 @@
<pre><code class="nohighlight">bw -a run node-1 "uptime"</code></pre>
-<div class="alert">The <code>-a</code> switch tells bw to automatically trust unknown SSH host keys (when you're connecting to a new node). By default, only known host keys will be accepted.</div>
+<div class="alert alert-info">The <code>-a</code> switch tells bw to automatically trust unknown SSH host keys (when you're connecting to a new node). By default, only known host keys will be accepted.</div>
You should see something like this:
Modified: packages/bundlewrap/trunk/docs/content/items/action.md
===================================================================
--- packages/bundlewrap/trunk/docs/content/items/action.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/docs/content/items/action.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -24,6 +24,12 @@
<br>
+### data_stdin
+
+You can pipe data directly to the command running on the node. To do so, use this attribute. If it's a string or unicode object, it will always be encoded as UTF-8. Alternatively, you can use raw bytes.
+
+<br>
+
### expected_return_code
Defaults to `0`. If the return code of your command is anything else, the action is considered failed. You can also set this to `None` and any return code will be accepted.
Modified: packages/bundlewrap/trunk/docs/content/repo/hooks.md
===================================================================
--- packages/bundlewrap/trunk/docs/content/repo/hooks.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/docs/content/repo/hooks.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -14,7 +14,7 @@
def node_apply_start(repo, node, interactive=False, **kwargs):
post_message("Starting apply on {}, everything is gonna be OK!".format(node.name))
-<div class="alert">Always define your hooks with `**kwargs` so we can pass in more information in future updates without breaking your hook.</div>
+<div class="alert alert-warning">Always define your hooks with <code>**kwargs</code> so we can pass in more information in future updates without breaking your hook.</div>
<br>
@@ -112,6 +112,48 @@
---
+**`lock_add(repo, node, lock_id, items, expiry, comment, **kwargs)`**
+
+Called each time a soft lock is added to a node.
+
+`repo` The current repository (instance of `bundlewrap.repo.Repository`).
+
+`node` The current node (instance of `bundlewrap.node.Node`).
+
+`lock_id` The random ID of the lock.
+
+`items` List of item selector strings.
+
+`expiry` UNIX timestamp of lock expiry time (int).
+
+`comment` As entered by user.
+
+---
+
+**`lock_remove(repo, node, lock_id, **kwargs)`**
+
+Called each time a soft lock is removed from a node.
+
+`repo` The current repository (instance of `bundlewrap.repo.Repository`).
+
+`node` The current node (instance of `bundlewrap.node.Node`).
+
+`lock_id` The random ID of the lock.
+
+---
+
+**`lock_show(repo, node, lock_info, **kwargs)`**
+
+Called each time `bw lock show` finds a lock on a node.
+
+`repo` The current repository (instance of `bundlewrap.repo.Repository`).
+
+`node` The current node (instance of `bundlewrap.node.Node`).
+
+`lock_info` A dict contain the lock details.
+
+---
+
**`node_apply_start(repo, node, interactive=False, **kwargs)`**
Called each time a `bw apply` command reaches a new node.
Modified: packages/bundlewrap/trunk/docs/content/repo/nodes.py.md
===================================================================
--- packages/bundlewrap/trunk/docs/content/repo/nodes.py.md 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/docs/content/repo/nodes.py.md 2017-07-06 18:52:08 UTC (rev 14175)
@@ -66,11 +66,21 @@
A string used as a DNS name when connecting to this node. May also be an IP address.
-<div class="alert">The username and SSH private key for connecting to the node cannot be configured in BundleWrap. If you need to customize those, BundleWrap will honor your <code>~/.ssh/config</code>.</div>
+<div class="alert alert-info">The username and SSH private key for connecting to the node cannot be configured in BundleWrap. If you need to customize those, BundleWrap will honor your <code>~/.ssh/config</code>.</div>
Cannot be set at group level.
+### magic_number
+
+A large number derived from the node's name. This number is very likely to be unique for your entire repository. You can, for example, use this number to easily "jitter" cronjobs:
+
+ '{} {} * * * root /my/script'.format(
+ node.magic_number % 60,
+ node.magic_number % 2 + 4,
+ )
+
+
### metadata
This can be a dictionary of arbitrary data (some type restrictions apply). You can access it from your templates as `node.metadata`. Use this to attach custom data (such as a list of IP addresses that should be configured on the target node) to the node. Note that you can also define metadata at the [group level](groups.py.md#metadata), but node metadata has higher priority.
Modified: packages/bundlewrap/trunk/setup.py
===================================================================
--- packages/bundlewrap/trunk/setup.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/setup.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -16,7 +16,7 @@
setup(
name="bundlewrap",
- version="2.18.1",
+ version="2.19.0",
description="Config management with Python",
long_description=(
"By allowing for easy and low-overhead config management, BundleWrap fills the gap between complex deployments using Chef or Puppet and old school system administration over SSH.\n"
Modified: packages/bundlewrap/trunk/tests/integration/bw_apply_actions.py
===================================================================
--- packages/bundlewrap/trunk/tests/integration/bw_apply_actions.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/tests/integration/bw_apply_actions.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -46,3 +46,51 @@
},
)
run("bw apply localhost", path=str(tmpdir))
+
+
+def test_action_pipe_binary(tmpdir):
+ make_repo(
+ tmpdir,
+ bundles={
+ "test": {
+ 'actions': {
+ "pipe": {
+ 'command': "cat",
+ 'data_stdin': b"hello\000world",
+ 'expected_stdout': b"hello\000world",
+ },
+ },
+ },
+ },
+ nodes={
+ "localhost": {
+ 'bundles': ["test"],
+ 'os': host_os(),
+ },
+ },
+ )
+ run("bw apply localhost", path=str(tmpdir))
+
+
+def test_action_pipe_utf8(tmpdir):
+ make_repo(
+ tmpdir,
+ bundles={
+ "test": {
+ 'actions': {
+ "pipe": {
+ 'command': "cat",
+ 'data_stdin': "hello ð§\n",
+ 'expected_stdout': "hello ð§\n",
+ },
+ },
+ },
+ },
+ nodes={
+ "localhost": {
+ 'bundles': ["test"],
+ 'os': host_os(),
+ },
+ },
+ )
+ run("bw apply localhost", path=str(tmpdir))
Modified: packages/bundlewrap/trunk/tests/integration/bw_metadata.py
===================================================================
--- packages/bundlewrap/trunk/tests/integration/bw_metadata.py 2017-07-05 13:24:50 UTC (rev 14174)
+++ packages/bundlewrap/trunk/tests/integration/bw_metadata.py 2017-07-06 18:52:08 UTC (rev 14175)
@@ -1,3 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from json import loads
from os.path import join
@@ -120,3 +123,54 @@
""")
stdout, stderr, rcode = run("bw metadata node1", path=str(tmpdir))
assert rcode == 1
+
+
+def test_table(tmpdir):
+ make_repo(
+ tmpdir,
+ nodes={
+ "node1": {
+ 'metadata': {
+ "foo_dict": {
+ "bar": "baz",
+ },
+ "foo_list": ["bar", 1],
+ "foo_int": 47,
+ "foo_umlaut": "föö",
+ },
+ },
+ "node2": {
+ 'metadata': {
+ "foo_dict": {
+ "baz": "bar",
+ },
+ "foo_list": [],
+ "foo_int": -3,
+ "foo_umlaut": "füü",
+ },
+ },
+ },
+ groups={
+ "all": {
+ 'members': ["node1", "node2"],
+ },
+ },
+ )
+ stdout, stderr, rcode = run("BW_TABLE_STYLE=grep bw metadata --table all foo_dict bar, foo_list, foo_int, foo_umlaut", path=str(tmpdir))
+ assert stdout.decode('utf-8') == """node\tfoo_dict bar\tfoo_list\tfoo_int\tfoo_umlaut
+node1\tbaz\tbar, 1\t47\tföö
+node2\t<missing>\t\t-3\tfüü
+"""
+ assert stderr == b""
+ assert rcode == 0
+
+
+def test_table_no_key(tmpdir):
+ make_repo(
+ tmpdir,
+ nodes={
+ "node1": {},
+ },
+ )
+ stdout, stderr, rcode = run("bw metadata --table node1", path=str(tmpdir))
+ assert rcode == 1
More information about the Python-apps-commits
mailing list