def _execute_command(self, ctx: HandlerContext, vyos, command, terminator, timeout=10): """Patch for wonky behavior of vymgmt, after exit it can no longer use the unique prompt""" conn = vyos._Router__conn conn.sendline(command) i = conn.expect([terminator, TIMEOUT], timeout=timeout) output = conn.before if isinstance(output, bytes): output = output.decode("utf-8") if not i == 0: ctx.debug("got raw result %(result)s", result=conn.before.decode(), cmd=command) raise vymgmt.VyOSError("Connection timed out") if not conn.prompt(): ctx.debug("got raw result %(result)s", result=conn.before.decode(), cmd=command) raise vymgmt.VyOSError("Connection timed out") return output
def read_resource(self, ctx: HandlerContext, resource: Config) -> None: if resource.facts: return vyos = self.get_connection(ctx, resource.id.version, resource) current = self.get_config_dict(ctx, resource, vyos) cfg = current for key in resource.node.split(" "): if isinstance(cfg, str): cfg = {cfg: {}} break elif key in cfg: cfg = cfg[key] else: raise ResourcePurged() ctx.debug( "Comparing desired with current", desired=resource.config, current=cfg, node=resource.node, raw_current=current, ) current_cfg = self._dict_to_path(resource.node, cfg) ctx.debug("Current paths", path=current_cfg) resource.config = current_cfg
def create_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: self._io.mkdir(resource.path) mode = str(resource.permissions) self._io.chmod(resource.path, mode) self._io.chown(resource.path, resource.owner, resource.group) ctx.set_created()
def create_resource(self, ctx: HandlerContext, resource: Config) -> None: if resource.facts: return ctx.debug("Creating resource, invalidating cache") self._invalidate_cache(resource) self._execute(ctx, resource, delete=False) ctx.set_created()
def read_resource(self, ctx: HandlerContext, resource: AgentConfig) -> None: agent_config = self._get_map() ctx.set("map", agent_config) if resource.agentname not in agent_config: raise ResourcePurged() resource.uri = agent_config[resource.agentname]
def update_resource(self, ctx: HandlerContext, changes: dict, resource: PurgeableResource) -> None: if "permissions" in changes: mode = str(resource.permissions) self._io.chmod(resource.path, mode) if "owner" in changes or "group" in changes: self._io.chown(resource.path, resource.owner, resource.group) ctx.set_updated()
def update_resource(self, ctx: HandlerContext, changes: dict, resource: Config) -> None: if resource.facts: return ctx.debug("Updating resource, invalidating cache") self._invalidate_cache(resource) self._execute(ctx, resource, delete=True) ctx.set_updated()
def post(self, ctx: HandlerContext, resource: Config) -> None: if self.connection: try: # Vyos cannot logout before exiting configuration mode self.connection.exit(force=True) self.connection.logout() self.connection = None except: # noqa: E722 ctx.exception("Failed to close connection")
def deploy( self, ctx: HandlerContext, resource: Resource, requires: Dict[ResourceIdStr, const.ResourceState], ) -> None: if self.skip(resource.id.agent_name, resource.key): raise SkipResource() elif self.fail(resource.id.agent_name, resource.key): raise Exception() elif resource.set_state_to_deployed: ctx.set_status(const.ResourceState.deployed)
def create_resource(self, ctx: HandlerContext, resource: Config) -> None: vyos = self.get_connection(ctx, resource.id.version, resource) # try old command first, new one hangs due to insufficient entropy cmd = "generate vpn rsa-key bits 2048 random /dev/urandom" result = vyos.run_op_mode_command(cmd) if "Invalid command:" in result: cmd = "generate vpn rsa-key bits 2048" result = vyos.run_op_mode_command(cmd) ctx.debug("got result %(result)s", result=result, cmd=cmd) assert "has been generated" in result
def delete_resource(self, ctx: HandlerContext, resource: Config) -> None: if resource.facts: return ctx.debug("Deleting resource, invalidating cache") self._invalidate_cache(resource) vyos = self.get_connection(ctx, resource.id.version, resource) vyos.configure() vyos.delete(resource.node) vyos.commit() if resource.save: vyos.save() vyos.exit(force=True) ctx.set_purged()
def facts(self, ctx: HandlerContext, resource: Config) -> None: vyos = self.get_connection(ctx, resource.id.version, resource) orig = current = self.get_config_dict(ctx, resource, vyos) path = resource.node.split(" ") for el in path: if el in current: current = current[el] else: ctx.debug("No value found", error=current, path=path, orig=orig) return {} return {"value": current}
def create_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: if self._io.file_exists(resource.path): raise Exception( f"Cannot create file {resource.path}, because it already exists." ) data = self.get_file(resource.hash) if hash_file(data) != resource.hash: raise Exception( "File hash was %s expected %s" % (resource.hash, hash_file(data)) ) self._io.put(resource.path, data) self._io.chmod(resource.path, str(resource.permissions)) self._io.chown(resource.path, resource.owner, resource.group) ctx.set_created()
def facts(self, ctx: HandlerContext, resource: IpFact) -> None: # example output # vyos@vyos:~$ show interfaces # Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down # Interface IP Address S/L Description # --------- ---------- --- ----------- # eth0 10.0.0.7/24 u/u # eth1 10.1.0.15/24 u/u # lo 127.0.0.1/8 u/u # ::1/128 try: vyos = self.get_connection(ctx, resource.id.version, resource) cmd = "show interfaces" interface = resource.interface result = vyos.run_op_mode_command(cmd).replace("\r", "") ctx.debug("got result %(result)s", result=result, cmd=cmd) parsed_lines = [ self.parse_line(line) for line in result.split("\n") ] parsed_lines = [line for line in parsed_lines if line is not None] # find right lines ips = itertools.dropwhile(lambda x: x[0] != interface, parsed_lines) ips = list( itertools.takewhile(lambda x: x[0] == interface or not x[0], ips)) ctx.debug("got ips %(ips)s", ips=ips) ips = [ip[1] for ip in ips] if not ips: return {} if len(ips) == 1: return {"ip_address": ips[0]} else: ips = sorted(ips) out = {"ip_address": ips[0]} for i, addr in enumerate(ips): out[f"ip_address_{i}"] = addr return out finally: self.post(ctx, resource)
def update_resource(self, ctx: HandlerContext, changes: dict, resource: PurgeableResource) -> None: if not self._io.file_exists(resource.path): raise Exception( f"Cannot update file {resource.path} because it doesn't exist") if "hash" in changes: data = self.get_file(resource.hash) if hash_file(data) != resource.hash: raise Exception("File hash was %s expected %s" % (resource.hash, hash_file(data))) self._io.put(resource.path, data) if "permissions" in changes: self._io.chmod(resource.path, str(resource.permissions)) if "owner" in changes or "group" in changes: self._io.chown(resource.path, resource.owner, resource.group) ctx.set_updated()
def _execute(self, ctx: handler.HandlerContext, events: dict, cache: AgentCache) -> (bool, bool): """ :param ctx The context to use during execution of this deploy :param events Possible events that are available for this resource :param cache The cache instance to use :return (success, send_event) Return whether the execution was successful and whether a change event should be sent to provides of this resource. """ ctx.debug("Start deploy %(deploy_id)s of resource %(resource_id)s", deploy_id=self.gid, resource_id=self.resource_id) provider = None try: provider = handler.Commander.get_provider(cache, self.scheduler.agent, self.resource) provider.set_cache(cache) except Exception: if provider is not None: provider.close() cache.close_version(self.resource.id.version) ctx.set_status(const.ResourceState.unavailable) ctx.exception("Unable to find a handler for %(resource_id)s", resource_id=str(self.resource.id)) return False, False yield self.scheduler.agent.thread_pool.submit(provider.execute, ctx, self.resource) send_event = (hasattr(self.resource, "send_event") and self.resource.send_event) if ctx.status is not const.ResourceState.deployed: provider.close() cache.close_version(self.resource.id.version) return False, send_event if len(events) > 0 and provider.can_process_events(): ctx.info( "Sending events to %(resource_id)s because of modified dependencies", resource_id=str(self.resource.id)) yield self.scheduler.agent.thread_pool.submit( provider.process_events, ctx, self.resource, events) provider.close() cache.close_version(self.resource_id.version) return True, send_event
def test_CRUD_handler_purged_response(purged_desired, purged_actual, excn, create, delete, updated, caplog): """ purged_actual and excn are conceptually equivalent, this test case serves to prove that they are in fact, equivalent """ caplog.set_level(logging.DEBUG) class DummyCrud(CRUDHandler): def __init__(self): self.updated = False self.created = False self.deleted = False def read_resource(self, ctx: HandlerContext, resource: resources.PurgeableResource) -> None: resource.purged = purged_actual if updated: resource.value = "b" if excn: raise ResourcePurged() def update_resource(self, ctx: HandlerContext, changes: dict, resource: resources.PurgeableResource) -> None: self.updated = True def create_resource(self, ctx: HandlerContext, resource: resources.PurgeableResource) -> None: self.created = True def delete_resource(self, ctx: HandlerContext, resource: resources.PurgeableResource) -> None: self.deleted = True @resource("aa::Aa", "aa", "aa") class TestResource(PurgeableResource): fields = ("value", ) res = TestResource(Id("aa::Aa", "aa", "aa", "aa", 1)) res.purged = purged_desired res.value = "a" ctx = HandlerContext(res, False) handler = DummyCrud() handler.execute(ctx, res, False) assert handler.updated == ((not (create or delete)) and updated and not purged_desired) assert handler.created == create assert handler.deleted == delete no_error_in_logs(caplog) log_contains(caplog, "inmanta.agent.handler", logging.DEBUG, "resource aa::Aa[aa,aa=aa],v=1: Calling read_resource")
def get_pubkey(self, ctx: HandlerContext, resource: Config) -> str: vyos = self.get_connection(ctx, resource.id.version, resource) cmd = "TERM=ansi show vpn ike rsa-keys" try: result = re.sub( "\x1b\\[[0-9]?[a-zA-Z]", "", vyos.run_op_mode_command(cmd).replace("\r", ""), ) except vymgmt.router.VyOSError: ctx.debug( "got raw raw result %(result)s", result=vyos._Router__conn.before.decode("utf-8"), cmd=cmd, ) raise ctx.debug("got raw result %(result)s", result=result, cmd=cmd) marker = "Local public key (/config/ipsec.d/rsa-keys/localhost.key):" if marker in result: idx = result.find(marker) result = result[idx + len(marker):] if "====" in result: idx = result.find("====") result = result[:idx] ctx.debug("got result %(result)s", result=result, cmd=cmd) result = result.strip() else: raise ResourcePurged() return result
def _execute(self, ctx: HandlerContext, resource: Config, delete: bool) -> None: commands = [x for x in resource.config.split("\n") if len(x) > 0] vyos = self.get_connection(ctx, resource.id.version, resource) try: vyos.configure() if delete and not resource.never_delete: ctx.debug("Deleting %(node)s", node=resource.node) vyos.delete(resource.node) for cmd in commands: ctx.debug("Setting %(cmd)s", cmd=cmd) if delete and resource.never_delete: try: vyos.delete(cmd) except vymgmt.ConfigError: pass vyos.set(cmd) vyos.commit() if resource.save: vyos.save() vyos.exit(force=True) except vymgmt.router.VyOSError: ctx.debug( "got raw raw result %(result)s", result=vyos._Router__conn.before.decode("utf-8"), cmd=cmd, ) raise
def update_resource(self, ctx: HandlerContext, changes: dict, resource: AgentConfig) -> None: agent_config = ctx.get("map") agent_config[resource.agentname] = resource.uri self._set_map(agent_config)
def delete_resource(self, ctx: HandlerContext, resource: AgentConfig) -> None: agent_config = ctx.get("map") del agent_config[resource.agentname] self._set_map(agent_config)
def _do_set_fact(self, ctx: HandlerContext, resource: PurgeableResource) -> None: ctx.set_fact(fact_id=resource.key, value=resource.value)
def test_context_changes(): """Test registering changes in the handler context""" resource = PurgeableResource( Id.parse_id("std::File[agent,path=/test],v=1")) ctx = HandlerContext(resource) # use attribute change attributes ctx.update_changes( {"value": AttributeStateChange(current="a", desired="b")}) assert len(ctx.changes) == 1 # use dict ctx.update_changes({"value": dict(current="a", desired="b")}) assert len(ctx.changes) == 1 assert isinstance(ctx.changes["value"], AttributeStateChange) # use dict with empty string ctx.update_changes({"value": dict(current="", desired="value")}) assert len(ctx.changes) == 1 assert ctx.changes["value"].current == "" assert ctx.changes["value"].desired == "value" # use tuple ctx.update_changes({"value": ("a", "b")}) assert len(ctx.changes) == 1 assert isinstance(ctx.changes["value"], AttributeStateChange) # use wrong arguments with pytest.raises(InvalidOperation): ctx.update_changes({"value": ("a", "b", 3)}) with pytest.raises(InvalidOperation): ctx.update_changes({"value": ["a", "b"]}) with pytest.raises(InvalidOperation): ctx.update_changes({"value": "test"})
def delete_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: self._io.remove(resource.target) ctx.set_purged()
def update_resource( self, ctx: HandlerContext, changes: dict, resource: PurgeableResource ) -> None: self._io.remove(resource.target) self._io.symlink(resource.source, resource.target) ctx.set_updated()
def create_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: self._io.symlink(resource.source, resource.target) ctx.set_created()
def delete_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: self._io.rmdir(resource.path) ctx.set_purged()
def delete_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> None: if self._io.file_exists(resource.path): self._io.remove(resource.path) ctx.set_purged()
def update_resource(self, ctx: HandlerContext, changes: dict, resource: ResourceResource) -> None: a = self.run_sync(self.test_sync) assert a == 5 ctx.set_updated()
def delete_resource(self, ctx: HandlerContext, resource: ResourceResource) -> None: ctx.set_purged()