From d30e84aad5baf6ecc4f76e4d6db6967544a77a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= <joerg@thalheim.io> Date: Mon, 29 Aug 2022 14:11:38 +0200 Subject: [PATCH] switch to deploykit from deploy_nixos --- deploy_nixos.py | 253 ------------------------------------------------ flake.lock | 45 +++++++++ flake.nix | 133 +++++++++++++++---------- shell.nix | 4 +- tasks.py | 2 +- 5 files changed, 129 insertions(+), 308 deletions(-) delete mode 100644 deploy_nixos.py diff --git a/deploy_nixos.py b/deploy_nixos.py deleted file mode 100644 index 7733f9d..0000000 --- a/deploy_nixos.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 - -import os -from contextlib import contextmanager -from typing import List, Dict, Tuple, IO, Iterator, Optional, Callable, Any, Union, Text -from threading import Thread -import subprocess -import fcntl -import select -from contextlib import ExitStack -from enum import Enum - - -@contextmanager -def pipe() -> Iterator[Tuple[IO[str], IO[str]]]: - (pipe_r, pipe_w) = os.pipe() - read_end = os.fdopen(pipe_r, "r") - write_end = os.fdopen(pipe_w, "w") - - try: - fl = fcntl.fcntl(read_end, fcntl.F_GETFL) - fcntl.fcntl(read_end, fcntl.F_SETFL, fl | os.O_NONBLOCK) - - yield (read_end, write_end) - finally: - read_end.close() - write_end.close() - - -FILE = Union[None, int] - - -class HostKeyCheck(Enum): - STRICT = 0 - # trust-on-first-use - TOFU = 1 - NONE = 2 - - -class DeployHost: - def __init__( - self, - host: str, - user: str = "root", - port: int = 22, - forward_agent: bool = False, - command_prefix: Optional[str] = None, - host_key_check: HostKeyCheck = HostKeyCheck.STRICT, - meta: Dict[str, Any] = {}, - ) -> None: - self.host = host - self.user = user - self.port = port - if command_prefix: - self.command_prefix = command_prefix - else: - self.command_prefix = host - self.forward_agent = forward_agent - self.host_key_check = host_key_check - self.meta = meta - - def _prefix_output( - self, print_fd: IO[str], stdout: Optional[IO[str]], stderr: Optional[IO[str]] - ) -> Tuple[str, str]: - rlist = [print_fd] - if stdout is not None: - rlist.append(stdout) - - if stderr is not None: - rlist.append(stderr) - - print_buf = "" - stdout_buf = "" - stderr_buf = "" - - while len(rlist) != 0: - r, _, _ = select.select(rlist, [], []) - - if print_fd in r: - read = os.read(print_fd.fileno(), 4096) - if len(read) == 0: - rlist.remove(print_fd) - print_buf += read.decode("utf-8") - if read == "" or "\n" in print_buf: - lines = print_buf.rstrip("\n").split("\n") - for line in lines: - print(f"[{self.command_prefix}] {line}") - print_buf = "" - - def handle_fd(fd: Optional[IO[Any]]) -> str: - if fd and fd in r: - read = os.read(fd.fileno(), 4096) - if len(read) == 0: - rlist.remove(fd) - else: - return read.decode("utf-8") - return "" - - stdout_buf += handle_fd(stdout) - stderr_buf += handle_fd(stderr) - return stdout_buf, stderr_buf - - def _run( - self, cmd: List[str], shell: bool, stdout: FILE = None, stderr: FILE = None - ) -> subprocess.CompletedProcess[Text]: - with ExitStack() as stack: - if stdout is None or stderr is None: - read_fd, write_fd = stack.enter_context(pipe()) - - if stdout is None: - stdout_read = None - stdout_write = write_fd - elif stdout == subprocess.PIPE: - stdout_read, stdout_write = stack.enter_context(pipe()) - - if stderr is None: - stderr_read = None - stderr_write = write_fd - elif stderr == subprocess.PIPE: - stderr_read, stderr_write = stack.enter_context(pipe()) - - with subprocess.Popen( - cmd, text=True, shell=shell, stdout=stdout_write, stderr=stderr_write - ) as p: - write_fd.close() - if stdout == subprocess.PIPE: - stdout_write.close() - if stderr == subprocess.PIPE: - stderr_write.close() - stdout_data, stderr_data = self._prefix_output(read_fd, stdout_read, stderr_read) - ret = p.wait() - return subprocess.CompletedProcess( - cmd, ret, stdout=stdout_data, stderr=stderr_data - ) - raise RuntimeError("unreachable") - - def run_local( - self, cmd: str, stdout: FILE = None, stderr: FILE = None - ) -> subprocess.CompletedProcess: - print(f"[{self.command_prefix}] {cmd}") - return self._run([cmd], shell=True, stdout=stdout, stderr=stderr) - - def run( - self, cmd: str, stdout: FILE = None, stderr: FILE = None - ) -> subprocess.CompletedProcess: - print(f"[{self.command_prefix}] {cmd}") - ssh_opts = ["-A"] if self.forward_agent else [] - - if self.host_key_check != HostKeyCheck.STRICT: - ssh_opts.extend(["-o", "StrictHostKeyChecking=no"]) - if self.host_key_check == HostKeyCheck.NONE: - ssh_opts.extend(["-o", "UserKnownHostsFile=/dev/null"]) - - ssh_cmd = ( - ["ssh", f"{self.user}@{self.host}", "-p", str(self.port)] - + ssh_opts - + ["--", cmd] - ) - return self._run(ssh_cmd, shell=False, stdout=stdout, stderr=stderr) - - -DeployResults = List[Tuple[DeployHost, subprocess.CompletedProcess[Text]]] - - -class DeployGroup: - def __init__(self, hosts: List[DeployHost]) -> None: - self.hosts = hosts - - def _run_local( - self, - cmd: str, - host: DeployHost, - results: DeployResults, - stdout: FILE = None, - stderr: FILE = None, - ) -> None: - results.append((host, host.run_local(cmd, stdout=stdout, stderr=stderr))) - - def _run_remote( - self, - cmd: str, - host: DeployHost, - results: DeployResults, - stdout: FILE = None, - stderr: FILE = None, - ) -> None: - results.append((host, host.run(cmd, stdout=stdout, stderr=stderr))) - - def _run( - self, cmd: str, local: bool = False, stdout: FILE = None, stderr: FILE = None - ) -> DeployResults: - results: DeployResults = [] - threads = [] - for host in self.hosts: - fn = self._run_local if local else self._run_remote - thread = Thread( - target=fn, - kwargs=dict( - results=results, cmd=cmd, host=host, stdout=stdout, stderr=stderr - ), - ) - thread.start() - threads.append(thread) - - for thread in threads: - thread.join() - - return results - - def run(self, cmd: str, stdout: FILE = None, stderr: FILE = None) -> DeployResults: - return self._run(cmd, stdout=stdout, stderr=stderr) - - def run_local( - self, cmd: str, stdout: FILE = None, stderr: FILE = None - ) -> DeployResults: - return self._run(cmd, local=True, stdout=stdout, stderr=stderr) - - def run_function(self, func: Callable) -> None: - threads = [] - for host in self.hosts: - thread = Thread( - target=func, - args=(host,), - ) - threads.append(thread) - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() - - -def parse_hosts( - hosts: str, - host_key_check: HostKeyCheck = HostKeyCheck.STRICT, - forward_agent: bool = False, -) -> DeployGroup: - deploy_hosts = [] - for h in hosts.split(","): - parts = h.split("@") - if len(parts) > 1: - user = parts[0] - hostname = parts[1] - else: - user = "root" - hostname = parts[0] - deploy_hosts.append( - DeployHost( - hostname, user=user, host_key_check=host_key_check, forward_agent=forward_agent - ) - ) - return DeployGroup(deploy_hosts) diff --git a/flake.lock b/flake.lock index e58dec7..901daf9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,28 @@ { "nodes": { + "deploykit": { + "inputs": { + "flake-parts": [ + "flake-parts" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1661774097, + "narHash": "sha256-dvWpOU3dU9TfSnK7I+CSDrCaASr+uR5VepG/SQs/RIg=", + "owner": "numtide", + "repo": "deploykit", + "rev": "c8cd9ddcd647e3116b07e920e53aebf047cbeac5", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "deploykit", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -16,6 +39,26 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1661009076, + "narHash": "sha256-phAE40gctVygRq3G3B6LhvD7u2qdQT21xsz8DdRDYFo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "850d8a76026127ef02f040fb0dcfdb8b749dd9d9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "hercules-ci-effects": { "inputs": { "nixpkgs": "nixpkgs" @@ -310,6 +353,8 @@ }, "root": { "inputs": { + "deploykit": "deploykit", + "flake-parts": "flake-parts", "hercules-ci-effects": "hercules-ci-effects", "hydra": "hydra", "nixpkgs": "nixpkgs_3", diff --git a/flake.nix b/flake.nix index 320ff6d..8bb5c06 100644 --- a/flake.nix +++ b/flake.nix @@ -21,63 +21,90 @@ hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects"; hydra.url = "github:NixOS/hydra"; hydra.inputs.nixpkgs.follows = "nixpkgs"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-parts.inputs.nixpkgs.follows = "nixpkgs"; + + deploykit.url = "github:numtide/deploykit"; + deploykit.inputs.nixpkgs.follows = "nixpkgs"; + deploykit.inputs.flake-parts.follows = "flake-parts"; }; - outputs = { self - , nixpkgs - , nixpkgs-update - , nixpkgs-update-github-releases - , nixpkgs-update-pypi-releases - , sops-nix - , hercules-ci-effects - , hydra - }: { - devShell.x86_64-linux = let - pkgs = nixpkgs.legacyPackages.x86_64-linux; - in pkgs.callPackage ./shell.nix { - inherit (sops-nix.packages.x86_64-linux) sops-import-keys-hook; - }; - nixosConfigurations = let - common = [ - sops-nix.nixosModules.sops - ]; - in { - "build01.nix-community.org" = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = common ++ [ - ./build01/configuration.nix - ]; - }; + outputs = { + self, + flake-parts, + ... + }: + (flake-parts.lib.evalFlakeModule + {inherit self;} + { + systems = ["x86_64-linux" "aarch64-linux" "x86_64-linux" "aarch64-darwin"]; - "build02.nix-community.org" = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = common ++ [ - (import ./build02/nixpkgs-update.nix { - inherit nixpkgs-update - nixpkgs-update-github-releases - nixpkgs-update-pypi-releases; - }) - ./build02/configuration.nix - ]; - }; + perSystem = { + inputs', + pkgs, + ... + }: { + devShells.default = pkgs.callPackage ./shell.nix { + inherit (inputs'.sops-nix.packages) sops-import-keys-hook; + inherit (inputs'.deploykit.packages) deploykit; + }; + }; + flake.nixosConfigurations = let + inherit (self.inputs.nixpkgs.lib) nixosSystem; + common = [ + self.inputs.sops-nix.nixosModules.sops + ]; + in { + "build01.nix-community.org" = nixosSystem { + system = "x86_64-linux"; + modules = + common + ++ [ + ./build01/configuration.nix + ]; + }; - "build03.nix-community.org" = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = common ++ [ - (import ./services/hydra { - inherit hydra; - }) + "build02.nix-community.org" = nixosSystem { + system = "x86_64-linux"; + modules = + common + ++ [ + (import ./build02/nixpkgs-update.nix { + inherit + (self.inputs) + nixpkgs-update + nixpkgs-update-github-releases + nixpkgs-update-pypi-releases + ; + }) + ./build02/configuration.nix + ]; + }; - ./build03/configuration.nix - ]; - }; + "build03.nix-community.org" = nixosSystem { + system = "x86_64-linux"; + modules = + common + ++ [ + (import ./services/hydra { + inherit (self.inputs) hydra; + }) - "build04.nix-community.org" = nixpkgs.lib.nixosSystem { - system = "aarch64-linux"; - modules = common ++ [ - ./build04/configuration.nix - ]; - }; - }; - }; + ./build03/configuration.nix + ]; + }; + + "build04.nix-community.org" = nixosSystem { + system = "aarch64-linux"; + modules = + common + ++ [ + ./build04/configuration.nix + ]; + }; + }; + }) + .config + .flake; } diff --git a/shell.nix b/shell.nix index 19935d0..aafdcf2 100644 --- a/shell.nix +++ b/shell.nix @@ -1,9 +1,10 @@ { pkgs ? import <nixpkgs> {} , sops-import-keys-hook +, deploykit }: with pkgs; -mkShell { +mkShellNoCC { sopsPGPKeyDirs = [ "./keys" ]; @@ -23,5 +24,6 @@ mkShell { rsync sops-import-keys-hook + deploykit ]; } diff --git a/tasks.py b/tasks.py index dc14a6a..ff64382 100644 --- a/tasks.py +++ b/tasks.py @@ -4,7 +4,7 @@ from invoke import task import sys from typing import List, Any -from deploy_nixos import DeployHost, DeployGroup +from deploykit import DeployHost, DeployGroup import subprocess import json