async def create_linux_user( kitchen: Kitchen, name: str, password_hash: Optional[str], create_home: bool = True, create_group: bool = True, home: Optional[str] = None, ) -> None: args = ["useradd"] if password_hash: # N.B. if you don't use a password hash, the account will be locked # but passwordless SSH still works args += ["-p", password_hash] if create_home: args.append("-m") if create_group: args.append("-U") else: args.append("-N") if home: args += ["-d", home] # finally, append the user name args.append(name) result = await kitchen.ut1areq(SimpleExec(args, "/"), SimpleExec.Result) if result.exit_code != 0: raise RuntimeError("Failed to create user. Error was: " + result.stderr.strip().decode())
async def cook_systemd_start(kitchen: Kitchen, unit_name: str): result = await kitchen.ut1areq( SimpleExec( ["systemctl", "start", unit_name], "/", ), SimpleExec.Result, ) if result.exit_code != 0: raise RuntimeError( f"Failed to start {unit_name}: {result.stderr.decode()}")
async def cook_systemd_daemon_reload(kitchen): result = await kitchen.ut1areq( SimpleExec( ["systemctl", "daemon-reload"], "/", ), SimpleExec.Result, ) if result.exit_code != 0: raise RuntimeError( f"Failed to reload systemd: {result.stderr.decode()}")
async def cook(self, kitchen: Kitchen) -> None: kitchen.get_dependency_tracker().ignore() changed = await kitchen.ut1( HasChangedInSousStore(self.purpose, self.watching)) if changed: result = await kitchen.ut1areq( SimpleExec(self.command, self.working_dir), SimpleExec.Result) if result.exit_code != 0: raise RuntimeError( f"exit code not 0 ({result.exit_code}), {result.stderr!r}")
async def _apt_command( self, kitchen: Kitchen, args: List[str] ) -> SimpleExec.Result: retries = 3 while retries > 0: result = await kitchen.ut1areq(SimpleExec(args, "/"), SimpleExec.Result) if result.exit_code == 0 or b"/lock" not in result.stderr: return result logger.warning( "Failed apt command due to suspected locking issue. Will retry…" ) retries -= 1 # /lock seen in stderr, probably a locking issue... lock_check = await kitchen.ut1areq( SimpleExec( ["fuser", "/var/lib/dpkg/lock", "/var/lib/apt/lists/lock"], "/" ), SimpleExec.Result, ) if lock_check.exit_code != 0: # non-zero code means the file is not being accessed; # use up a retry (N.B. we retry because this could be racy...) logger.warning( "Suspected locking issue is either racy or a red herring." ) retries -= 1 await asyncio.sleep(2.0) return result # noqa
async def cook(self, k: "Kitchen"): res = await k.ut1areq(SimpleExec(["tar", "xf", self.tar], self.dir), SimpleExec.Result) if res.exit_code != 0: raise RuntimeError( f"tar failed with ec {res.exit_code}; stderr = <<<" f"\n{res.stderr.decode()}\n>>>") for expect_relative in self.expect_files: expect = str(Path(self.dir, expect_relative)) stat = await k.ut1a(Stat(expect), Stat.Result) if stat is None: raise RuntimeError( f"tar succeeded but expectation failed; {expect!r} not found." )
async def cook_systemd_enable(kitchen: Kitchen, enabled: bool, unit_name: str): # thoughts: we could find the unit path with: # systemctl show -p FragmentPath apache2.service result = await kitchen.ut1areq( SimpleExec( ["systemctl", "enable" if enabled else "disable", unit_name], "/", ), SimpleExec.Result, ) if result.exit_code != 0: raise RuntimeError( f"Failed to en/disable {unit_name}: {result.stderr.decode()}")
async def exec_no_fails( kitchen: Kitchen, args: List[str], working_dir: Union[str, PurePath]) -> SimpleExec.Result: if not isinstance(working_dir, str): working_dir = str(working_dir) result = await kitchen.start_and_consume_attrs( SimpleExec(args, working_dir), SimpleExec.Result) if result.exit_code != 0: recipe: Optional[Recipe] = current_recipe.get(None) # type: ignore if recipe: raise ExecutionFailure( args, working_dir, recipe.recipe_context.sous, recipe.recipe_context.user, result, ) else: raise ExecutionFailure(args, working_dir, "???", "???", result) return result
async def cook(self, k: Kitchen) -> None: # no non-arg dependencies k.get_dependency_tracker() stat = await k.ut1a(Stat(self.dest_dir), Stat.Result) if stat is None: # doesn't exist; git init it await exec_no_fails(k, ["git", "init", self.dest_dir], "/") stat = await k.ut1a(Stat(self.dest_dir), Stat.Result) if stat is None: raise RuntimeError("Directory vanished after creation!") if not stat.dir: raise RuntimeError("Already exists but not a dir: " + self.dest_dir) # add the remote, removing it first to ensure it's what we want # don't care if removing fails await k.ut1areq( SimpleExec(["git", "remote", "remove", "scone"], self.dest_dir), SimpleExec.Result, ) await exec_no_fails(k, ["git", "remote", "add", "scone", self.repo_src], self.dest_dir) # fetch the latest from the remote await exec_no_fails(k, ["git", "fetch", "scone"], self.dest_dir) # figure out what ref we want to use # TODO(performance): fetch only this ref? ref = self.ref or f"scone/{self.branch}" # switch to that ref await exec_no_fails(k, ["git", "switch", "--detach", ref], self.dest_dir) # if we use submodules if self.submodules: await exec_no_fails( k, ["git", "submodule", "update", "--init", "--recursive"], self.dest_dir, ) for expected in self.expect: expected_path_str = str(Path(self.dest_dir, expected)) # TODO(performance, low): parallelise these stat = await k.ut1a(Stat(expected_path_str), Stat.Result) if not stat: raise RuntimeError( f"expected {expected_path_str} to exist but it did not") if stat.dir and not expected.endswith("/"): raise RuntimeError( f"expected {expected_path_str} to exist as a file but it is a dir" ) if not stat.dir and expected.endswith("/"): raise RuntimeError( f"expected {expected_path_str} to exist as a dir but it is a file" )