def verify(self): if self._delayed: self._render() try: with open(self.path, 'rb') as target: current = target.read() if current == self.content: return except FileNotFoundError: current = b'' except Exception: output.annotate('Unknown content - can\'t predict diff.') raise batou.UpdateNeeded() encoding = self.encoding or 'ascii' current_text = current.decode(encoding, errors='replace') wanted_text = self.content.decode(encoding) for line in difflib.unified_diff(current_text.splitlines(), wanted_text.splitlines()): line = line.replace('\n', '') if not line.strip(): continue output.annotate('\t{} {}'.format(os.path.basename(self.path), line), red=line.startswith('-'), green=line.startswith('+')) raise batou.UpdateNeeded()
def verify(self): with open(self.path, 'r') as target: current = target.read() if current != self.cookie: raise batou.UpdateNeeded() current = os.stat(self.path).st_mode if stat.S_IMODE(current) != 0o400: raise batou.UpdateNeeded()
def verify(self): if not os.path.exists(self.known_hosts): raise batou.UpdateNeeded() with open(self.known_hosts, 'r') as f: content = f.read() if self.port == 22: match = self.hostname else: match = '[{}]:{}'.format(self.hostname, self.port) if match not in content: raise batou.UpdateNeeded()
def assert_component_is_current(self, requirements=[], **kw): """Assert that this component has been updated more recently than the components specified in the ``requirements``, raise :py:class:`UpdateNeeded` otherwise. :param list requirements: The list of components you want to check against. :return: ``None``, if this component is as new or newer as all ``requirements``. :param dict kw: Arguments that are passed through to each ``last_update`` call. The semantics depend on the components' implementations. :raises UpdateNeeded: if this component is older than any of the ``requirements``. The age of a component is determined by calling ``last_updated`` on this and each requirement component. """ if isinstance(requirements, Component): requirements = [requirements] reference = self.last_updated(**kw) if reference is None: output.annotate( "assert_component_is_current({}, ...): No reference".format( self._breadcrumb), debug=True, ) raise batou.UpdateNeeded() for requirement in requirements: self |= requirement required = requirement.last_updated(**kw) if required is None: continue if reference < required: output.annotate( "assert_component_is_current({}, {}): {} < {}".format( self._breadcrumb, requirement._breadcrumb, reference, required, ), debug=True, ) raise batou.UpdateNeeded()
def verify(self): cmd_out, cmt_err = self.pgcmd( self.expand("psql -d {{component.db}} -qtAX " '-c "SELECT extname FROM pg_extension ' "WHERE extname = '{{component.extension_name}}';\"")) if cmd_out.strip() != self.extension_name: raise batou.UpdateNeeded()
def deploy(self, predict_only=False): # Remember: this is a tight loop - we need to keep this code fast. # Reset changed flag here to support triggering deploy() multiple # times. This is mostly helpful for testing, but who knows. self.changed = False for sub_component in self.sub_components: sub_component.deploy(predict_only) if sub_component.changed: self.changed = True if not os.path.exists(self.workdir): os.makedirs(self.workdir) with self.chdir(self.workdir), self: try: with batou.utils.Timer('{} verify()'.format( self._breadcrumbs)): try: self.verify() except Exception: if predict_only: # XXX?!?!? raise batou.UpdateNeeded() raise except batou.UpdateNeeded: self.__trigger_event__('before-update', predict_only=predict_only) output.annotate(self._breadcrumbs) if not predict_only: self.update() self.changed = True
def verify(self): try: self.pgcmd( self.expand('psql -c "SELECT true;" -d "{{component.db}}"'), silent=True) except batou.utils.CmdExecutionError: raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd('rabbitmqctl -q list_users') users = stdout.splitlines() for line in users: if not line: continue user, tags = line.split('\t', 1) if user == self.username: raise batou.UpdateNeeded()
def verify(self): if self.attribute: stdout, stderr = self.cmd("nix-env -qaA {{component.attribute}}") self._installs_package = stdout.strip() else: self._installs_package = self.package stdout, stderr = self.cmd("nix-env --query") if self._installs_package not in stdout.splitlines(): raise batou.UpdateNeeded()
def verify(self): os.environ['PGPASSWORD'] = self.password try: self.cmd(self.expand( 'psql -d postgres -c "SELECT true;" -U {{component.name}} ' '-w -h localhost')) except batou.utils.CmdExecutionError: raise batou.UpdateNeeded() finally: del os.environ['PGPASSWORD']
def verify(self): try: self._select_stat_implementation() except AttributeError: # Happens on systems without lstat/lchmod implementation (like # Linux) Not sure whether ignoring it is really the right thing. return current = self._stat(self.path).st_mode if stat.S_IMODE(current) != self.mode: raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd('rsync {} {}{}/ {}'.format( self.verify_opts, self.exclude_arg, self.source, self.path)) # In case of we see non-convergent rsync runs output.annotate('rsync result:', debug=True) output.annotate(stdout, debug=True) if len(stdout.strip().splitlines()) - 4 > 0: raise batou.UpdateNeeded()
def assert_cmd(self, *args, **kw): """Assert that given command returns successfully, raise :py:class:`UpdateNeeded` otherwise. For details about the command arguments and what a successful execution means, see :py:func:`batou.component.Component.cmd`. """ try: self.cmd(*args, **kw) except batou.utils.CmdExecutionError: raise batou.UpdateNeeded()
def verify_pkg(self, pkg): try: self.cmd('bin/python -c "' "import pkg_resources; " "assert pkg_resources.require('{}')[0].parsed_version == " "pkg_resources.parse_version('{}')\"".format( pkg.package, pkg.version)) except CmdExecutionError: raise batou.UpdateNeeded() # Is the package usable? Is the package a module? This might be # overspecific - I'm looking for a way to deal with: # https://github.com/pypa/pip/issues/3 if a namespace package was not # installed cleanly. This only works (currently), when the package name # corresponds with what it contains. I.E. it works for zc.buildout but # not for distribute, which installs a setuptools package. if pkg.check_package_is_module: try: self.cmd('bin/python -c "import pkg_resources; ' 'import {0};{0}.__file__"'.format(pkg.package)) except CmdExecutionError: raise batou.UpdateNeeded()
def verify(self): if self._delayed: self._render() with open(self.path, 'rb') as target: current = target.read() if current == self.content: return if self.encoding: current_text = current.decode(self.encoding, errors='replace') wanted_text = self.content.decode(self.encoding) for line in difflib.unified_diff(current_text.splitlines(), wanted_text.splitlines()): output.annotate(line, debug=True) raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd( 'rabbitmqctl -q list_user_permissions {{component.username}}') lines = stdout.splitlines() to_validate = self.permissions.copy() self.to_update = [] self.to_delete = [] for line in lines: vhost, conf, write, read = line.split('\t', 3) perms = to_validate.pop(vhost, None) if perms is None: self.to_delete.append(vhost) elif perms != (conf, write, read): self.to_update.append(vhost) self.to_update.extend(to_validate.keys()) if self.to_update or self.to_delete: raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd('rabbitmqctl -q list_users') users = stdout.splitlines() self.create = True self.set_tags = True for line in users: if not line: continue user, tags = line.split('\t', 1) tags = sorted(tags[1:-1].split(', ')) if user == self.username: self.create = False if tags == self.tags: self.set_tags = False break if self.create or self.set_tags: raise batou.UpdateNeeded()
def assert_no_changes(self): """Assert that, during this run of batou, neither this component nor any of its sub-components have required an update. :return: ``None``, if neither this component nor any of its sub-components have required an update during this run of batou. :raises UpdateNeeded: if this component or any of its sub-components have required an update during this run of batou. .. note:: Using this change indicator can be unreliable if you fail to perform your update correctly. It is likely that when later resuming an aborted deployment this change won't be triggered again. """ if self.changed: raise batou.UpdateNeeded() self.assert_no_subcomponent_changes()
def verify(self): if not os.path.exists(self.target): raise batou.UpdateNeeded() if self.checksum != batou.utils.hash(self.target, self.checksum_function): raise batou.UpdateNeeded()
def verify(self): if glob.glob(self.pattern): raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd("nix-env --query") if self.package in stdout.splitlines(): raise batou.UpdateNeeded()
def verify(self, predicting=False): try: if self._delayed: self._render() except FileNotFoundError: if predicting: # During prediction runs we accept that delayed rending may # not yet work and that we will change. We might want to # turn this into an explicit flag so we don't implicitly # run into a broken deployment. assert False # If we are not predicting then this is definitely a problem. # Stop here. raise try: with open(self.path, "rb") as target: current = target.read() if current == self.content: return except FileNotFoundError: current = b"" except Exception: output.annotate("Unknown content - can't predict diff.") raise batou.UpdateNeeded() if self.encoding: current_text = current.decode(self.encoding, errors="replace") wanted_text = self.content.decode(self.encoding, errors="replace") if not self.encoding: output.annotate("Not showing diff for binary data.", yellow=True) elif self.sensitive_data: output.annotate("Not showing diff as it contains sensitive data.", red=True) else: current_lines = current_text.splitlines() wanted_lines = wanted_text.splitlines() words = set( itertools.chain(*(x.split() for x in current_lines), *(x.split() for x in wanted_lines))) contains_secrets = bool( self.environment.secret_data.intersection(words)) diff = difflib.unified_diff(current_lines, wanted_lines) if not os.path.exists(self.diff_dir): os.makedirs(self.diff_dir) diff, diff_too_long, diff_log = limited_buffer( diff, self._max_diff, self._max_diff_lead, logdir=self.diff_dir) if diff_too_long: output.line( f"More than {self._max_diff} lines of diff. Showing first " f"and last {self._max_diff_lead} lines.", yellow=True) output.line(f"see {diff_log} for the full diff.".format(), yellow=True) if contains_secrets: output.line("Not showing diff as it contains sensitive data,", yellow=True) output.line(f"see {diff_log} for the diff.".format(), yellow=True) else: for line in diff: line = line.replace("\n", "") if not line.strip(): continue output.annotate(f" {os.path.basename(self.path)} {line}", red=line.startswith("-"), green=line.startswith("+")) raise batou.UpdateNeeded()
def verify(self): if isinstance(self.owner, str): self.owner = pwd.getpwnam(self.owner).pw_uid current = os.stat(self.path).st_uid if current != self.owner: raise batou.UpdateNeeded()
def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()
def verify(self): current = os.stat(self.path).st_gid if current != self.group: raise batou.UpdateNeeded()
def verify(self): stdout, stderr = self.cmd('rabbitmqctl -q list_vhosts') vhosts = stdout.splitlines() if self.name not in vhosts: raise batou.UpdateNeeded()
def verify(self): if not os.path.islink(self.target): raise batou.UpdateNeeded() if os.readlink(self.target) != self.source: raise batou.UpdateNeeded()
def verify(self): try: self.cmd("nix-env --query {{component.package}}") raise batou.UpdateNeeded() except batou.utils.CmdExecutionError: pass
def verify(self): log.append('{}:verify'.format(self.id)) raise batou.UpdateNeeded()