diff --git a/packages/grid/devspace.yaml b/packages/grid/devspace.yaml index bb6fb6d482a..dde53587f46 100644 --- a/packages/grid/devspace.yaml +++ b/packages/grid/devspace.yaml @@ -325,16 +325,15 @@ profiles: path: deployments.seaweedfs-config commands: + start: + command: |- + tox -e dev.k8s.start deploy: command: |- - devspace use context k3d-syft-dev - devspace use namespace syft - devspace deploy --var CONTAINER_REGISTRY=k3d-registry.localhost:12345 + devspace deploy --kube-context k3d-syft-dev --namespace syft --var CONTAINER_REGISTRY=k3d-registry.localhost:12345 dev: command: |- - devspace use context k3d-syft-dev - devspace use namespace syft - devspace dev --var CONTAINER_REGISTRY=k3d-registry.localhost:12345 - contexts: + devspace dev --kube-context k3d-syft-dev --namespace syft --var CONTAINER_REGISTRY=k3d-registry.localhost:12345 + info: command: |- - kubectl config get-contexts + tox -e dev.k8s.info diff --git a/scripts/patch_hosts.py b/scripts/patch_hosts.py new file mode 100644 index 00000000000..56dc0f25185 --- /dev/null +++ b/scripts/patch_hosts.py @@ -0,0 +1,212 @@ +# stdlib +import argparse +from functools import cached_property +import os +from pathlib import Path +import platform +import re +import sys + + +class Platform: + system = platform.system() + uname = platform.uname() + + @staticmethod + def get() -> str: + return Platform.system + + @staticmethod + def windows() -> bool: + return Platform.system == "Windows" + + @staticmethod + def macos() -> bool: + return Platform.system == "Darwin" + + @staticmethod + def linux() -> bool: + return Platform.system == "Linux" + + @staticmethod + def wsl2() -> bool: + return "wsl2" in Platform.uname.release.lower() + + +class Hosts: + def __init__(self, path: str = None) -> None: + self.__path = path + self.content = self.read() + + @cached_property + def path(self) -> Path: + if self.__path: + p = self.__path + elif Platform.linux(): + p = "/etc/hosts" + elif Platform.macos(): + new_hosts = "/etc/hosts" + old_hosts = "/private/etc/hosts" + p = new_hosts if os.path.exists(new_hosts) else old_hosts + elif Platform.windows(): + p = "C:\Windows\System32\drivers\etc\hosts" + else: + raise Exception(f"Unsupported OS: {Platform.system}") + + p = Path(p).absolute() + assert p.exists(), "Host file does not exist" + return p + + def read(self) -> str: + return self.path.read_text() + + def get(self, domain: str) -> list[str]: + return re.findall(f"(.+)\s+{domain}", self.content) + + def add(self, ip: str, domain: str) -> None: + if self.get(domain): + return + + self.content = self.content.rstrip() + f"\n{ip}\t{domain}" + self.__write() + + def remove(self, domain: str) -> None: + if not self.get(domain): + return + + self.content = re.sub(f"(.+)\s+{domain}\n", "", self.content) + self.__write() + + def update(self, ip: str, domain: str) -> None: + if not self.get(domain): + self.add(ip, domain) + + # inplace + self.content = re.sub(f"(.+)\s+{domain}\n", f"{ip}\t{domain}\n", self.content) + self.__write() + + def __write(self) -> None: + cleaned = re.sub("\n{2}\n+", "\n", self.content) + self.path.write_text(cleaned.rstrip() + "\n") + + +def running_as_root() -> bool: + if not Platform.windows(): + return os.geteuid() == 0 + else: + # stdlib + import ctypes + + return ctypes.windll.shell32.IsUserAnAdmin() == 1 + + +def wsl2_disable_auto_hosts() -> None: + if not Platform.wsl2(): + return + + # stdlib + import configparser + + conf_path = Path("/etc/wsl.conf") + conf = configparser.ConfigParser() + conf.optionxform = str + conf.read(conf_path) + + if "network" not in conf: + conf["network"] = {} + + if conf["network"]["generateHosts"] != "false": + conf["network"]["generateHosts"] = "false" + with conf_path.open("w") as fp: + conf.write(fp) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--add", + nargs=2, + action="append", + default=[], + metavar=("IP", "DOMAIN"), + help="Add entry to hosts file", + ) + parser.add_argument( + "--add-k3d-registry", + action="store_true", + default=False, + help="Add entry for k3d-registry.localhost", + ) + parser.add_argument( + "--fix-docker-hosts", + action="store_true", + default=False, + help="Windows - Fix *.docker.internal. Linux/macOS - remove them", + ) + parser.add_argument( + "--disable-wsl2-auto-hosts", + action="store_true", + default=False, + dest="wsl2_disable_auto_hosts", + help="[Optional] Disable automatic /etc/hosts generation from Windows in WSL2", + ) + parser.add_argument( + "--hosts", + type=Path, + default=None, + help="[Optional] Path to a hosts-like file", + ) + + if len(sys.argv) < 2: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + + if not args.hosts and not running_as_root(): + print( + "ERROR: This script must be run as root since it will modify system hosts file" + ) + sys.exit(1) + + hosts = Hosts(args.hosts) + + print(">> Args", args.__dict__) + print(">> OS:", Platform.system) + print(">> Release:", Platform.uname.release) + print(">> Version:", Platform.uname.version) + print(">> Hosts file:", hosts.path) + + if len(args.add): + for ip, domain in args.add: + print(f">> Adding {ip} {domain}") + hosts.update(ip, domain) + + if args.add_k3d_registry: + print(">> Adding k3d registry host entry") + hosts.update("127.0.0.1", "k3d-registry.localhost") + + if args.fix_docker_hosts: + if Platform.windows() or Platform.wsl2(): + print(">> Fixing docker host entries for Windows/WSL2") + hosts.update("0.0.0.0", "host.docker.internal") + hosts.update("0.0.0.0", "gateway.docker.internal") + hosts.update("127.0.0.1", "kubernetes.docker.internal") + else: + print(">> Removing docker host entries") + hosts.remove("host.docker.internal") + hosts.remove("gateway.docker.internal") + hosts.remove("kubernetes.docker.internal") + + if args.wsl2_disable_auto_hosts and Platform.wsl2(): + print(">> Disabling auto hosts generation") + wsl2_disable_auto_hosts() + + print(">> Done") + print("-" * 50) + print(hosts.read()) + print("-" * 50) + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini index eead6745fe8..8afb2e94815 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,11 @@ [tox] envlist = + dev.k8s.registry + dev.k8s.start + dev.k8s.deploy + dev.k8s.info + dev.k8s.destroy + dev.k8s.destroyall hagrid.publish lint stack.test.integration @@ -936,52 +942,79 @@ commands = pytest [testenv:dev.k8s.registry] -changedir = {toxinidir}/packages/grid +description = Start local Kubernetes registry with k3d +changedir = {toxinidir} passenv=HOME,USER allowlist_externals = bash + sudo commands = - bash -c 'k3d registry create registry.localhost --port 12345 -v $HOME/.k3d-registry:/var/lib/registry' - bash -c '\ - CYAN="\033[1;36m"; \ - CLEAR="\033[0m"; \ - printf "\n${CYAN}---------------------------------------------------------\n" ;\ - printf "Make sure to add the following to your /etc/hosts file:${CLEAR}\n";\ - printf "127.0.0.1 k3d-registry.localhost\n";\ - printf "${CYAN}---------------------------------------------------------${CLEAR}\n"' + bash -c 'k3d registry create registry.localhost --port 12345 -v $HOME/.k3d-registry:/var/lib/registry || true' + sudo {envpython} scripts/patch_hosts.py --add-k3d-registry --fix-docker-hosts [testenv:dev.k8s.start] -changedir = {toxinidir}/packages/grid +description = Start local Kubernetes registry & cluster with k3d +changedir = {toxinidir} +passenv=* allowlist_externals = bash - k3d + sleep + tox commands = - bash -c 'URL=http://k3d-registry.localhost:12345/v2/_catalog; \ - curl -X GET $URL || (echo "ERROR: Add \"127.0.0.1 k3d-registry.localhost\" to /etc/hosts" && exit 1)' + tox -e dev.k8s.registry + sleep 3 + bash -c 'URL=http://k3d-registry.localhost:12345/v2/_catalog; curl -X GET $URL' bash -c 'k3d cluster create syft-dev -p "8080:80@loadbalancer" --registry-use k3d-registry.localhost:12345; \ - k3d cluster list' + kubectl create namespace syft;' + tox -e dev.k8s.info [testenv:dev.k8s.deploy] +description = Deploy to a local Kubernetes cluster with Devspace changedir = {toxinidir}/packages/grid passenv=HOME, USER allowlist_externals = bash commands = - bash -c '\ - devspace use context k3d-syft-dev; \ - devspace use namespace syft; \ - devspace deploy --var CONTAINER_REGISTRY=k3d-registry.localhost:12345 --config=devspace.yaml' + bash -c 'devspace deploy -b --kube-context k3d-syft-dev --namespace syft --var CONTAINER_REGISTRY=k3d-registry.localhost:12345' + +[testenv:dev.k8s.info] +description = Gather info about the localKubernetes cluster +passenv=HOME, USER +ignore_errors = True +allowlist_externals = + k3d + kubectl +commands = + kubectl config view + k3d cluster list + kubectl cluster-info + kubectl config current-context + kubectl get namespaces -[testenv:dev.k8s.cleanup] +[testenv:dev.k8s.destroy] +description = Destroy local Kubernetes cluster changedir = {toxinidir}/packages/grid +passenv=HOME, USER allowlist_externals = bash commands = bash -c '\ - devspace use context k3d-syft-dev; \ - devspace use namespace syft; \ - devspace purge; \ + devspace purge --kube-context k3d-syft-dev --namespace syft; \ rm -rf .devspace; echo ""; \ k3d cluster delete syft-dev; echo ""; \ - k3d registry delete registry.localhost; echo "";\ kubectl config view' + +[testenv:dev.k8s.destroyall] +description = Destroy both local Kubernetes cluster and registry +changedir = {toxinidir} +passenv=HOME, USER +ignore_errors=True +allowlist_externals = + bash + tox + sudo + rm +commands = + tox -e dev.k8s.destroy + bash -c 'k3d registry delete registry.localhost || true' + sudo rm -rf ~/.k3d-registry