From d4862301193d8323dd74ace4d1949be76a86c829 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 20 Mar 2024 14:16:57 +0530 Subject: [PATCH 001/114] [syft] use uv in backend --- packages/.dockerignore | 8 +- packages/grid/backend/backend.dockerfile | 115 +++++--------------- packages/grid/backend/grid/start.sh | 18 +-- packages/grid/backend/worker_cpu.dockerfile | 10 +- packages/syft/.dockerignore | 2 - packages/syft/setup.cfg | 6 +- 6 files changed, 45 insertions(+), 114 deletions(-) delete mode 100644 packages/syft/.dockerignore diff --git a/packages/.dockerignore b/packages/.dockerignore index a8628d4acb1..cc6790bca71 100644 --- a/packages/.dockerignore +++ b/packages/.dockerignore @@ -1,9 +1,11 @@ +**/.pytest_cache +**/.mypy_cache **/*.pyc +**/__pycache__ +**/tests/ +**/README.md grid/data grid/packer grid/.devspace syftcli - -syft/tests -syft/README.md diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 1520190f0e1..d0706a39729 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,94 +1,43 @@ -ARG PYTHON_VERSION="3.12" -ARG TZ="Etc/UTC" - -# change to USER="syftuser", UID=1000 and HOME="/home/$USER" for rootless -ARG USER="root" -ARG UID=0 -ARG USER_GRP=$USER:$USER -ARG HOME="/root" -ARG APPDIR="$HOME/app" - -# ==================== [BUILD STEP] Python Dev Base ==================== # - -FROM cgr.dev/chainguard/wolfi-base as python_dev - -ARG PYTHON_VERSION -ARG TZ -ARG USER -ARG UID - -# Setup Python DEV -RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ - apk update && \ - apk upgrade && \ - apk add build-base gcc tzdata python-$PYTHON_VERSION-dev-default py$PYTHON_VERSION-pip && \ - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -# uncomment for creating rootless user -# && adduser -D -u $UID $USER +FROM cgr.dev/chainguard/wolfi-base as backend -# ==================== [BUILD STEP] Install Syft Dependency ==================== # +ARG PYTHON_VERSION="3.12" -FROM python_dev as syft_deps +RUN apk update && apk upgrade && \ + apk add git bash python-$PYTHON_VERSION-default uv=0.1.22-r0 -ARG APPDIR -ARG HOME -ARG UID -ARG USER -ARG USER_GRP +WORKDIR /root/app -USER $USER -WORKDIR $APPDIR -ENV PATH=$PATH:$HOME/.local/bin +# keep static deps separate to have each layer cached independently -# copy skeleton to do package install -COPY --chown=$USER_GRP \ - syft/setup.py \ - syft/setup.cfg \ - syft/pyproject.toml \ - syft/MANIFEST.in \ - syft/ +RUN --mount=type=cache,target=/root/.cache,sharing=locked \ + uv venv && \ + uv pip install torch==2.2.1+cpu --index-url https://download.pytorch.org/whl/cpu -COPY --chown=$USER_GRP \ - syft/src/syft/VERSION \ - syft/src/syft/capnp \ - syft/src/syft/ +RUN --mount=type=cache,target=/root/.cache,sharing=locked \ + uv pip install jupyterlab==4.1.5 -# Install all dependencies together here to avoid any version conflicts across pkgs -RUN --mount=type=cache,id=pip-$UID,target=$HOME/.cache/pip,uid=$UID,gid=$UID,sharing=locked \ - pip install --user --default-timeout=300 torch==2.2.1 -f https://download.pytorch.org/whl/cpu/torch_stable.html && \ - pip install --user pip-autoremove jupyterlab -e ./syft[data_science] && \ - pip-autoremove ansible ansible-core -y +COPY --chown=nonroot:nonroot \ + syft/setup.py syft/setup.cfg syft/pyproject.toml ./syft/ -# ==================== [Final] Setup Syft Server ==================== # +COPY --chown=nonroot:nonroot \ + syft/src/syft/VERSION ./syft/src/syft/ -FROM cgr.dev/chainguard/wolfi-base as backend +RUN --mount=type=cache,target=/root/.cache,sharing=locked \ + uv pip install -e ./syft[data_science,telemetry] && \ + uv pip freeze | grep ansible | xargs uv pip uninstall -# inherit from global -ARG APPDIR -ARG HOME -ARG PYTHON_VERSION -ARG TZ -ARG USER -ARG USER_GRP +# Copy syft source (in rootless mode) -# Setup Python -RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ - apk update && \ - apk upgrade && \ - apk add tzdata git bash python-$PYTHON_VERSION-default py$PYTHON_VERSION-pip && \ - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \ - # Uncomment for rootless user - # adduser -D -u 1000 $USER && \ - mkdir -p /var/log/pygrid $HOME/data/creds $HOME/data/db $HOME/.cache $HOME/.local -# chown -R $USER_GRP /var/log/pygrid $HOME/ +COPY --chown=nonroot:nonroot \ + grid/backend/grid grid/backend/worker_cpu.dockerfile ./grid/ -USER $USER -WORKDIR $APPDIR +# copy syft +COPY --chown=nonroot:nonroot \ + syft ./syft/ # Update environment variables -ENV PATH=$PATH:$HOME/.local/bin \ - PYTHONPATH=$APPDIR \ - APPDIR=$APPDIR \ +ENV \ + APPDIR="/root/app" \ NODE_NAME="default_node_name" \ NODE_TYPE="domain" \ SERVICE_NAME="backend" \ @@ -104,16 +53,6 @@ ENV PATH=$PATH:$HOME/.local/bin \ MONGO_HOST="localhost" \ MONGO_PORT="27017" \ MONGO_USERNAME="root" \ - MONGO_PASSWORD="example" \ - CREDENTIALS_PATH="$HOME/data/creds/credentials.json" - -# Copy pre-built jupyterlab, syft dependencies -COPY --chown=$USER_GRP --from=syft_deps $HOME/.local $HOME/.local - -# copy grid -COPY --chown=$USER_GRP grid/backend/grid grid/backend/worker_cpu.dockerfile ./grid/ - -# copy syft -COPY --chown=$USER_GRP syft/ ./syft/ + MONGO_PASSWORD="example" CMD ["bash", "./grid/start.sh"] diff --git a/packages/grid/backend/grid/start.sh b/packages/grid/backend/grid/start.sh index 2880800eee4..eef4d48a12f 100755 --- a/packages/grid/backend/grid/start.sh +++ b/packages/grid/backend/grid/start.sh @@ -1,8 +1,7 @@ #! /usr/bin/env bash set -e -echo "Running start.sh with RELEASE=${RELEASE} and $(id)" -export GEVENT_MONKEYPATCH="False" +echo "Running Syft with RELEASE=${RELEASE} and $(id)" APP_MODULE=grid.main:app LOG_LEVEL=${LOG_LEVEL:-info} @@ -10,34 +9,27 @@ HOST=${HOST:-0.0.0.0} PORT=${PORT:-80} NODE_TYPE=${NODE_TYPE:-domain} APPDIR=${APPDIR:-$HOME/app} - RELOAD="" DEBUG_CMD="" -# For debugging permissions -ls -lisa $HOME/data -ls -lisa $APPDIR/syft/ -ls -lisa $APPDIR/grid/ - if [[ ${DEV_MODE} == "True" ]]; then - echo "DEV_MODE Enabled" RELOAD="--reload" - pip install --user -e "$APPDIR/syft[telemetry,data_science]" fi # only set by kubernetes to avoid conflict with docker tests if [[ ${DEBUGGER_ENABLED} == "True" ]]; then - pip install --user debugpy + uv pip install debugpy DEBUG_CMD="python -m debugpy --listen 0.0.0.0:5678 -m" fi -set +e +source $APPDIR/.venv/bin/activate + export NODE_PRIVATE_KEY=$(python $APPDIR/grid/bootstrap.py --private_key) export NODE_UID=$(python $APPDIR/grid/bootstrap.py --uid) export NODE_TYPE=$NODE_TYPE -set -e +export GEVENT_MONKEYPATCH="False" echo "NODE_UID=$NODE_UID" echo "NODE_TYPE=$NODE_TYPE" diff --git a/packages/grid/backend/worker_cpu.dockerfile b/packages/grid/backend/worker_cpu.dockerfile index 2c859f30676..4abf634b176 100644 --- a/packages/grid/backend/worker_cpu.dockerfile +++ b/packages/grid/backend/worker_cpu.dockerfile @@ -12,7 +12,6 @@ ARG SYFT_VERSION_TAG="0.8.5-beta.9" FROM openmined/grid-backend:${SYFT_VERSION_TAG} -ARG PYTHON_VERSION="3.12" ARG SYSTEM_PACKAGES="" ARG PIP_PACKAGES="pip --dry-run" ARG CUSTOM_CMD='echo "No custom commands passed"' @@ -21,10 +20,7 @@ ARG CUSTOM_CMD='echo "No custom commands passed"' ENV SYFT_WORKER="true" ENV SYFT_VERSION_TAG=${SYFT_VERSION_TAG} -# Commenting this until we support built using python docker sdk or find any other alternative. -# RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \ -# --mount=type=cache,target=$HOME/.cache/pip,sharing=locked \ -RUN apk update && \ +RUN apk update && apk upgrade && \ apk add ${SYSTEM_PACKAGES} && \ - pip install --user ${PIP_PACKAGES} && \ - bash -c "$CUSTOM_CMD" + uv pip install ${PIP_PACKAGES} && \ + bash -c ". .venv/bin/activate && $CUSTOM_CMD" diff --git a/packages/syft/.dockerignore b/packages/syft/.dockerignore deleted file mode 100644 index fcac49cb125..00000000000 --- a/packages/syft/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -.mypy_cache -**/.mypy_cache diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 2440172d448..6186de5eb0b 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -88,7 +88,11 @@ data_science = evaluate==0.4.1 recordlinkage==0.16 dm-haiku==0.0.10 - torch[cpu]==2.2.1 + +torch = + # torch[cpu] doesn't work anymore and --index-url does not work with setup.cfg + # don't include this in data_science because CUDA libs will get bundled in backend.dockerfile + torch==2.2.1 dev = %(test_plugins)s From 5253f1867644184373fb6b6d6d1fb9194d84db5e Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Thu, 21 Mar 2024 16:21:11 +0530 Subject: [PATCH 002/114] [syft] support for arm64 builds --- packages/grid/backend/backend.dockerfile | 45 ++++++++++++++++++------ 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index d0706a39729..6ff9fdf531b 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,17 +1,28 @@ -FROM cgr.dev/chainguard/wolfi-base as backend - ARG PYTHON_VERSION="3.12" +ARG UV_VERSION="0.1.22-r0" +ARG TORCH_VERSION="2.2.1" + +# ==================== [BUILD STEP] Python Dev Base ==================== # +FROM cgr.dev/chainguard/wolfi-base as syft_deps + +ARG PYTHON_VERSION +ARG UV_VERSION +ARG TORCH_VERSION +# Setup Python DEV RUN apk update && apk upgrade && \ - apk add git bash python-$PYTHON_VERSION-default uv=0.1.22-r0 + apk add build-base gcc python-$PYTHON_VERSION-dev-default uv=$UV_VERSION WORKDIR /root/app # keep static deps separate to have each layer cached independently - +# if amd64 then we need to append +cpu to the torch version +# limitation of uv - https://github.com/astral-sh/uv/issues/2541 RUN --mount=type=cache,target=/root/.cache,sharing=locked \ uv venv && \ - uv pip install torch==2.2.1+cpu --index-url https://download.pytorch.org/whl/cpu + ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \ + if [[ "$ARCH" = "amd64" ]]; then TORCH_VERSION="$TORCH_VERSION+cpu"; fi && \ + uv pip install torch==$TORCH_VERSION --index-url https://download.pytorch.org/whl/cpu RUN --mount=type=cache,target=/root/.cache,sharing=locked \ uv pip install jupyterlab==4.1.5 @@ -26,17 +37,31 @@ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ uv pip install -e ./syft[data_science,telemetry] && \ uv pip freeze | grep ansible | xargs uv pip uninstall -# Copy syft source (in rootless mode) -COPY --chown=nonroot:nonroot \ - grid/backend/grid grid/backend/worker_cpu.dockerfile ./grid/ +# ==================== [Final] Setup Syft Server ==================== # + +FROM cgr.dev/chainguard/wolfi-base as backend + +ARG PYTHON_VERSION +ARG UV_VERSION + +RUN apk update && apk upgrade && \ + apk add git bash python-$PYTHON_VERSION-default uv=$UV_VERSION + +WORKDIR /root/app/ + +# Copy pre-built jupyterlab, syft dependencies +COPY --from=syft_deps /root/app/.venv .venv + +# copy grid +COPY grid/backend/grid grid/backend/worker_cpu.dockerfile ./grid/ # copy syft -COPY --chown=nonroot:nonroot \ - syft ./syft/ +COPY syft ./syft/ # Update environment variables ENV \ + PATH="/root/app/.venv/bin:$PATH" \ APPDIR="/root/app" \ NODE_NAME="default_node_name" \ NODE_TYPE="domain" \ From e7348ccad75bb0531d1ab9acce2e0fe235ab36f4 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 26 Mar 2024 13:52:13 +0530 Subject: [PATCH 003/114] [syft] add pip and upgrade uv --- packages/grid/backend/backend.dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 6ff9fdf531b..b6b708f3be8 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,5 +1,5 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.22-r0" +ARG UV_VERSION="0.1.24-r0" ARG TORCH_VERSION="2.2.1" # ==================== [BUILD STEP] Python Dev Base ==================== # @@ -46,7 +46,7 @@ ARG PYTHON_VERSION ARG UV_VERSION RUN apk update && apk upgrade && \ - apk add git bash python-$PYTHON_VERSION-default uv=$UV_VERSION + apk add git bash python-$PYTHON_VERSION-default py$PYTHON_VERSION-pip uv=$UV_VERSION WORKDIR /root/app/ @@ -61,7 +61,8 @@ COPY syft ./syft/ # Update environment variables ENV \ - PATH="/root/app/.venv/bin:$PATH" \ + # "activates" venv + PATH="/root/app/.venv/bin/:$PATH" \ APPDIR="/root/app" \ NODE_NAME="default_node_name" \ NODE_TYPE="domain" \ @@ -70,6 +71,7 @@ ENV \ DEV_MODE="False" \ DEBUGGER_ENABLED="False" \ CONTAINER_HOST="docker" \ + SINGLE_CONTAINER_MODE="True" \ OBLV_ENABLED="False" \ OBLV_LOCALHOST_PORT=3030 \ DEFAULT_ROOT_EMAIL="info@openmined.org" \ From bbac7139cec01a3f2850fb65626ab3ec568ca0c0 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Fri, 29 Mar 2024 13:24:08 +0530 Subject: [PATCH 004/114] [syft] fixes + use dockerfile.dockerignore --- .dockerignore | 11 --- .gitignore | 1 + packages/.dockerignore | 11 --- packages/grid/backend/backend.dockerfile | 20 +++--- .../backend/backend.dockerfile.dockerignore | 67 +++++++++++++++++++ packages/grid/backend/grid/start.sh | 1 + packages/grid/frontend/.dockerignore | 4 -- .../frontend/frontend.dockerfile.dockerignore | 10 +++ .../seaweedfs.dockerfile.dockerignore | 66 ++++++++++++++++++ .../veilid/veilid.dockerfile.dockerignore | 66 ++++++++++++++++++ packages/log.txt | 0 11 files changed, 221 insertions(+), 36 deletions(-) delete mode 100644 .dockerignore delete mode 100644 packages/.dockerignore create mode 100644 packages/grid/backend/backend.dockerfile.dockerignore delete mode 100644 packages/grid/frontend/.dockerignore create mode 100644 packages/grid/frontend/frontend.dockerfile.dockerignore create mode 100644 packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore create mode 100644 packages/grid/veilid/veilid.dockerfile.dockerignore delete mode 100644 packages/log.txt diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 36bccce0507..00000000000 --- a/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -.tox -.git -.vscode -scripts -.mypy_cache -.benchmarks -docker -packages/syft/src/target -packages/grid/apps/domain/src/nodedatabase.db -packages/grid/apps/network/src/nodedatabase.db -packages/grid/apps/worker/src/nodedatabase.db diff --git a/.gitignore b/.gitignore index 33dc85c251c..931f11ec51f 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ nohup.out notebooks/helm/scenario_data.jsonl # tox syft.build.helm generated file out.txt +packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore diff --git a/packages/.dockerignore b/packages/.dockerignore deleted file mode 100644 index 513221a7d61..00000000000 --- a/packages/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -**/.pytest_cache -**/.mypy_cache -**/*.pyc -**/__pycache__ -**/tests/ -**/README.md - -grid/* -!grid/backend - -syftcli diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index b6b708f3be8..656a24aad2d 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,5 +1,5 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.24-r0" +ARG UV_VERSION="0.1.26-r0" ARG TORCH_VERSION="2.2.1" # ==================== [BUILD STEP] Python Dev Base ==================== # @@ -24,17 +24,15 @@ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ if [[ "$ARCH" = "amd64" ]]; then TORCH_VERSION="$TORCH_VERSION+cpu"; fi && \ uv pip install torch==$TORCH_VERSION --index-url https://download.pytorch.org/whl/cpu -RUN --mount=type=cache,target=/root/.cache,sharing=locked \ - uv pip install jupyterlab==4.1.5 +# RUN --mount=type=cache,target=/root/.cache,sharing=locked \ +# uv pip install jupyterlab==4.1.5 -COPY --chown=nonroot:nonroot \ - syft/setup.py syft/setup.cfg syft/pyproject.toml ./syft/ +COPY syft/setup.py syft/setup.cfg syft/pyproject.toml ./syft/ -COPY --chown=nonroot:nonroot \ - syft/src/syft/VERSION ./syft/src/syft/ +COPY syft/src/syft/VERSION ./syft/src/syft/ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ - uv pip install -e ./syft[data_science,telemetry] && \ + uv pip install -e ./syft[data_science,telemetry] && \ uv pip freeze | grep ansible | xargs uv pip uninstall @@ -46,7 +44,7 @@ ARG PYTHON_VERSION ARG UV_VERSION RUN apk update && apk upgrade && \ - apk add git bash python-$PYTHON_VERSION-default py$PYTHON_VERSION-pip uv=$UV_VERSION + apk add --no-cache git bash python-$PYTHON_VERSION-default py$PYTHON_VERSION-pip uv=$UV_VERSION WORKDIR /root/app/ @@ -61,8 +59,10 @@ COPY syft ./syft/ # Update environment variables ENV \ - # "activates" venv + # "activate" venv PATH="/root/app/.venv/bin/:$PATH" \ + VIRTUAL_ENV="/root/app/.venv" \ + # Syft APPDIR="/root/app" \ NODE_NAME="default_node_name" \ NODE_TYPE="domain" \ diff --git a/packages/grid/backend/backend.dockerfile.dockerignore b/packages/grid/backend/backend.dockerfile.dockerignore new file mode 100644 index 00000000000..c5bacaa51c3 --- /dev/null +++ b/packages/grid/backend/backend.dockerfile.dockerignore @@ -0,0 +1,67 @@ +# Syft +tests/ +*.md + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# macOS +.DS_Store diff --git a/packages/grid/backend/grid/start.sh b/packages/grid/backend/grid/start.sh index eef4d48a12f..a902adecb52 100755 --- a/packages/grid/backend/grid/start.sh +++ b/packages/grid/backend/grid/start.sh @@ -14,6 +14,7 @@ DEBUG_CMD="" if [[ ${DEV_MODE} == "True" ]]; then + echo "DEV_MODE Enabled" RELOAD="--reload" fi diff --git a/packages/grid/frontend/.dockerignore b/packages/grid/frontend/.dockerignore deleted file mode 100644 index 00df28f40b9..00000000000 --- a/packages/grid/frontend/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -node_modules -.svelte-kit -.pnpm-store \ No newline at end of file diff --git a/packages/grid/frontend/frontend.dockerfile.dockerignore b/packages/grid/frontend/frontend.dockerfile.dockerignore new file mode 100644 index 00000000000..90f9f7be934 --- /dev/null +++ b/packages/grid/frontend/frontend.dockerfile.dockerignore @@ -0,0 +1,10 @@ +# Frontend +*.md + +# Dependency directories +node_modules +.svelte-kit +.pnpm-store + +# macOS +.DS_Store diff --git a/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore b/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore new file mode 100644 index 00000000000..298280a5b63 --- /dev/null +++ b/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore @@ -0,0 +1,66 @@ +# SeaweedFS +*.md + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# macOS +.DS_Store diff --git a/packages/grid/veilid/veilid.dockerfile.dockerignore b/packages/grid/veilid/veilid.dockerfile.dockerignore new file mode 100644 index 00000000000..14f5a4a07e9 --- /dev/null +++ b/packages/grid/veilid/veilid.dockerfile.dockerignore @@ -0,0 +1,66 @@ +# Veilid +*.md + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# macOS +.DS_Store diff --git a/packages/log.txt b/packages/log.txt deleted file mode 100644 index e69de29bb2d..00000000000 From 1de78de36fed45fb981c5fa2c460c0ffe7a7de79 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 15:49:04 +0530 Subject: [PATCH 005/114] [syft] update torch uv --- packages/grid/backend/backend.dockerfile | 9 +++------ packages/syft/setup.cfg | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 656a24aad2d..a6b6867b367 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,6 +1,6 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.26-r0" -ARG TORCH_VERSION="2.2.1" +ARG UV_VERSION="0.1.29-r0" +ARG TORCH_VERSION="2.2.2" # ==================== [BUILD STEP] Python Dev Base ==================== # FROM cgr.dev/chainguard/wolfi-base as syft_deps @@ -24,9 +24,6 @@ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ if [[ "$ARCH" = "amd64" ]]; then TORCH_VERSION="$TORCH_VERSION+cpu"; fi && \ uv pip install torch==$TORCH_VERSION --index-url https://download.pytorch.org/whl/cpu -# RUN --mount=type=cache,target=/root/.cache,sharing=locked \ -# uv pip install jupyterlab==4.1.5 - COPY syft/setup.py syft/setup.cfg syft/pyproject.toml ./syft/ COPY syft/src/syft/VERSION ./syft/src/syft/ @@ -48,7 +45,7 @@ RUN apk update && apk upgrade && \ WORKDIR /root/app/ -# Copy pre-built jupyterlab, syft dependencies +# Copy pre-built syft dependencies COPY --from=syft_deps /root/app/.venv .venv # copy grid diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 53d5d71ee02..d13ae0bf5c4 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -92,7 +92,7 @@ data_science = torch = # torch[cpu] doesn't work anymore and --index-url does not work with setup.cfg # don't include this in data_science because CUDA libs will get bundled in backend.dockerfile - torch==2.2.1 + torch==2.2.2 dev = %(test_plugins)s From c51c025a246b893cf9da9167f9599fd9b8e474c3 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 16:32:01 +0530 Subject: [PATCH 006/114] [syft] fix worker_cpu.dockerfile --- packages/grid/backend/worker_cpu.dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grid/backend/worker_cpu.dockerfile b/packages/grid/backend/worker_cpu.dockerfile index cb09611af11..4f85b6f990c 100644 --- a/packages/grid/backend/worker_cpu.dockerfile +++ b/packages/grid/backend/worker_cpu.dockerfile @@ -21,6 +21,7 @@ ENV SYFT_WORKER="true" ENV SYFT_VERSION_TAG=${SYFT_VERSION_TAG} RUN apk update && apk upgrade && \ - apk add ${SYSTEM_PACKAGES} && \ - uv pip install ${PIP_PACKAGES} && \ - bash -c ". .venv/bin/activate && $CUSTOM_CMD" + apk add --no-cache ${SYSTEM_PACKAGES} && \ + # if uv is present then run uv pip install else simple pip install + if [ -x "$(command -v uv)" ]; then uv pip install --no-cache ${PIP_PACKAGES}; else pip install --user ${PIP_PACKAGES}; fi && \ + bash -c "$CUSTOM_CMD" From 6d16bb69b887d2d0e6fb3719c73ede8edfd61d8a Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 16:33:34 +0530 Subject: [PATCH 007/114] [syft] fix worker_cpu.dockerfile --- packages/grid/backend/worker_cpu.dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grid/backend/worker_cpu.dockerfile b/packages/grid/backend/worker_cpu.dockerfile index 4f85b6f990c..8c6416373b7 100644 --- a/packages/grid/backend/worker_cpu.dockerfile +++ b/packages/grid/backend/worker_cpu.dockerfile @@ -12,6 +12,7 @@ ARG SYFT_VERSION_TAG="0.8.7-beta.0" FROM openmined/grid-backend:${SYFT_VERSION_TAG} +ARG PYTHON_VERSION="3.12" ARG SYSTEM_PACKAGES="" ARG PIP_PACKAGES="pip --dry-run" ARG CUSTOM_CMD='echo "No custom commands passed"' From 93ca8f2acb71463a83e119b04ac43b452fbd49ed Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 16:35:59 +0530 Subject: [PATCH 008/114] [syft] fix gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 931f11ec51f..330782e5fca 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,4 @@ nohup.out notebooks/helm/scenario_data.jsonl # tox syft.build.helm generated file out.txt -packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore + From 94acf1d0aacac1ff784d8988025c8cc0b454b46e Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 16:43:50 +0530 Subject: [PATCH 009/114] [syft] uv http timeout --- packages/grid/backend/backend.dockerfile | 2 ++ packages/grid/backend/worker_cpu.dockerfile | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 622a5fe9b07..c367b1af184 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -15,6 +15,8 @@ RUN apk update && apk upgrade && \ WORKDIR /root/app +ENV UV_HTTP_TIMEOUT=600 + # keep static deps separate to have each layer cached independently # if amd64 then we need to append +cpu to the torch version # limitation of uv - https://github.com/astral-sh/uv/issues/2541 diff --git a/packages/grid/backend/worker_cpu.dockerfile b/packages/grid/backend/worker_cpu.dockerfile index 19bbb844a7a..fc58f7f46a9 100644 --- a/packages/grid/backend/worker_cpu.dockerfile +++ b/packages/grid/backend/worker_cpu.dockerfile @@ -18,8 +18,9 @@ ARG PIP_PACKAGES="pip --dry-run" ARG CUSTOM_CMD='echo "No custom commands passed"' # Worker specific environment variables go here -ENV SYFT_WORKER="true" -ENV SYFT_VERSION_TAG=${SYFT_VERSION_TAG} +ENV SYFT_WORKER="true" \ + SYFT_VERSION_TAG=${SYFT_VERSION_TAG} \ + UV_HTTP_TIMEOUT=600 RUN apk update && apk upgrade && \ apk add --no-cache ${SYSTEM_PACKAGES} && \ From 848864c3724899d3d4f5c5d0871f869c04040c61 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Mon, 8 Apr 2024 17:13:21 +0530 Subject: [PATCH 010/114] [syft] remove oblv --- packages/grid/backend/backend.dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index c367b1af184..19cbcefe86a 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -70,8 +70,6 @@ ENV \ DEV_MODE="False" \ DEBUGGER_ENABLED="False" \ CONTAINER_HOST="docker" \ - OBLV_ENABLED="False" \ - OBLV_LOCALHOST_PORT=3030 \ DEFAULT_ROOT_EMAIL="info@openmined.org" \ DEFAULT_ROOT_PASSWORD="changethis" \ STACK_API_KEY="changeme" \ From 53ed988b73fede78e5ed515b85c22e912eae3758 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 10 Apr 2024 13:40:04 +0530 Subject: [PATCH 011/114] [veilid] remove dockerignore --- .../veilid/veilid.dockerfile.dockerignore | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 packages/grid/veilid/veilid.dockerfile.dockerignore diff --git a/packages/grid/veilid/veilid.dockerfile.dockerignore b/packages/grid/veilid/veilid.dockerfile.dockerignore deleted file mode 100644 index 14f5a4a07e9..00000000000 --- a/packages/grid/veilid/veilid.dockerfile.dockerignore +++ /dev/null @@ -1,66 +0,0 @@ -# Veilid -*.md - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# macOS -.DS_Store From b8ee6693bb0473dc9d5d928de18de825b392eea3 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 10 Apr 2024 14:44:58 +0530 Subject: [PATCH 012/114] [syft] fix worker not starting --- packages/grid/backend/grid/bootstrap.py | 3 +-- packages/grid/backend/grid/start.sh | 1 + packages/grid/devspace.yaml | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/grid/backend/grid/bootstrap.py b/packages/grid/backend/grid/bootstrap.py index 84fedc36fdf..0da833a3a39 100644 --- a/packages/grid/backend/grid/bootstrap.py +++ b/packages/grid/backend/grid/bootstrap.py @@ -26,8 +26,7 @@ def get_env(key: str, default: str = "") -> str | None: return None -DEFAULT_CREDENTIALS_PATH = os.path.expandvars("$HOME/data/creds/credentials.json") -CREDENTIALS_PATH = str(get_env("CREDENTIALS_PATH", DEFAULT_CREDENTIALS_PATH)) +CREDENTIALS_PATH = str(get_env("CREDENTIALS_PATH", "credentials.json")) NODE_PRIVATE_KEY = "NODE_PRIVATE_KEY" NODE_UID = "NODE_UID" diff --git a/packages/grid/backend/grid/start.sh b/packages/grid/backend/grid/start.sh index a902adecb52..9823620fe6a 100755 --- a/packages/grid/backend/grid/start.sh +++ b/packages/grid/backend/grid/start.sh @@ -27,6 +27,7 @@ fi source $APPDIR/.venv/bin/activate +export CREDENTIALS_PATH=${CREDENTIALS_PATH:-$HOME/data/creds/credentials.json} export NODE_PRIVATE_KEY=$(python $APPDIR/grid/bootstrap.py --private_key) export NODE_UID=$(python $APPDIR/grid/bootstrap.py --uid) export NODE_TYPE=$NODE_TYPE diff --git a/packages/grid/devspace.yaml b/packages/grid/devspace.yaml index 93d4922532f..bc11dfecca1 100644 --- a/packages/grid/devspace.yaml +++ b/packages/grid/devspace.yaml @@ -33,7 +33,8 @@ vars: images: backend: image: "${CONTAINER_REGISTRY}/${DOCKER_IMAGE_BACKEND}" - buildKit: { args: ["--platform", "linux/${PLATFORM}"] } + buildKit: + args: ["--target", "backend", "--platform", "linux/${PLATFORM}"] dockerfile: ./backend/backend.dockerfile context: ../ tags: @@ -50,7 +51,8 @@ images: - dev-${DEVSPACE_TIMESTAMP} seaweedfs: image: "${CONTAINER_REGISTRY}/${DOCKER_IMAGE_SEAWEEDFS}" - buildKit: {} + buildKit: + args: ["--platform", "linux/${PLATFORM}"] dockerfile: ./seaweedfs/seaweedfs.dockerfile context: ./seaweedfs tags: From b9b57cc0e4ed44e365cbb9c7648e70d5eef241b0 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 17 Apr 2024 13:02:00 +0530 Subject: [PATCH 013/114] [syft] another way to fix torch cpu --- packages/grid/backend/backend.dockerfile | 4 +++- packages/syft/setup.cfg | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 19cbcefe86a..75ef55ec5ba 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,5 +1,5 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.29-r0" +ARG UV_VERSION="0.1.32-r0" ARG TORCH_VERSION="2.2.2" # ==================== [BUILD STEP] Python Dev Base ==================== # @@ -31,6 +31,8 @@ COPY syft/setup.py syft/setup.cfg syft/pyproject.toml ./syft/ COPY syft/src/syft/VERSION ./syft/src/syft/ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ + # remove torch because we already have the cpu version pre-installed + sed --in-place /torch==/d ./syft/setup.cfg && \ uv pip install -e ./syft[data_science,telemetry] && \ uv pip freeze | grep ansible | xargs uv pip uninstall diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 23682268c7f..025fcc12658 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -89,11 +89,7 @@ data_science = evaluate==0.4.1 recordlinkage==0.16 dm-haiku==0.0.10 - -torch = - # torch[cpu] doesn't work anymore and --index-url does not work with setup.cfg - # don't include this in data_science because CUDA libs will get bundled in backend.dockerfile - torch==2.2.2 + torch==2.2.2 # this gets removed in backend.dockerfile so update the version over there as well! dev = %(test_plugins)s From 1030dd601555dacbd73225bdcfde590610c333b5 Mon Sep 17 00:00:00 2001 From: Koen van der Veen Date: Fri, 3 May 2024 17:03:36 +0200 Subject: [PATCH 014/114] fix tab completion, repr for services --- packages/syft/src/syft/client/api.py | 62 +++++++++++++++++++++++-- packages/syft/src/syft/client/client.py | 3 ++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index d60c6460b4f..48c300898a2 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -48,10 +48,12 @@ from ..service.warnings import WarningContext from ..types.cache_object import CachedSyftObject from ..types.identity import Identity +from ..types.syft_object import SYFT_OBJECT_VERSION_1 from ..types.syft_object import SYFT_OBJECT_VERSION_2 from ..types.syft_object import SyftBaseObject from ..types.syft_object import SyftMigrationRegistry from ..types.syft_object import SyftObject +from ..types.syft_object import list_dict_repr_html from ..types.uid import LineageID from ..types.uid import UID from ..util.autoreload import autoreload_enabled @@ -66,6 +68,19 @@ from ..service.job.job_stash import Job +try: + # third party + from IPython.core.guarded_eval import EVALUATION_POLICIES + + ipython = get_ipython() # type: ignore + ipython.Completer.evaluation = "limited" + EVALUATION_POLICIES["limited"].allowed_getattr_external.add( + ("syft.client.api", "APIModule") + ) +except Exception: + pass + + class APIRegistry: __api_registry__: dict[tuple, SyftAPI] = OrderedDict() @@ -585,6 +600,19 @@ def wrapper(*args: Any, **kwargs: Any) -> SyftError | Any: return wrapper +class APISubModulesView(SyftObject): + __canonical_name__ = "APISubModulesView" + __version__ = SYFT_OBJECT_VERSION_1 + + submodule: str = "" + endpoints: list[str] = [] + + __syft_include_id_coll_repr__ = False + + def _coll_repr_(self) -> dict[str, Any]: + return {"submodule": self.submodule, "endpoints": "\n".join(self.endpoints)} + + @serializable() class APIModule: _modules: list[str] @@ -596,6 +624,9 @@ def __init__(self, path: str, refresh_callback: Callable | None) -> None: self.path = path self.refresh_callback = refresh_callback + def __dir__(self) -> list[str]: + return self._modules + ["path"] + def has_submodule(self, name: str) -> bool: """We use this as hasattr() triggers __getattribute__ which triggers recursion""" try: @@ -610,7 +641,7 @@ def _add_submodule( setattr(self, attr_name, module_or_func) self._modules.append(attr_name) - def __getattribute__(self, name: str) -> Any: + def __getattr__(self, name: str) -> Any: try: return object.__getattribute__(self, name) except AttributeError: @@ -638,7 +669,31 @@ def __getitem__(self, key: str | int) -> Any: def _repr_html_(self) -> Any: if not hasattr(self, "get_all"): - return NotImplementedError + + def recursively_get_submodules( + module: APIModule | Callable, + ) -> list[APIModule | Callable]: + children = [module] + if isinstance(module, APIModule): + for submodule_name in module._modules: + submodule = getattr(module, submodule_name) + children += recursively_get_submodules(submodule) + return children + + views = [] + for submodule_name in self._modules: + submodule = getattr(self, submodule_name) + children = recursively_get_submodules(submodule) + child_paths = [ + x.path for x in children if isinstance(x, RemoteFunction) + ] + views.append( + APISubModulesView(submodule=submodule_name, endpoints=child_paths) + ) + + return list_dict_repr_html(views) + # return NotImplementedError + results = self.get_all() return results._repr_html_() @@ -764,9 +819,6 @@ class SyftAPI(SyftObject): __user_role: ServiceRole = ServiceRole.NONE communication_protocol: PROTOCOL_TYPE - # def __post_init__(self) -> None: - # pass - @staticmethod def for_user( node: AbstractNode, diff --git a/packages/syft/src/syft/client/client.py b/packages/syft/src/syft/client/client.py index 9438294a6c0..5bf007599e4 100644 --- a/packages/syft/src/syft/client/client.py +++ b/packages/syft/src/syft/client/client.py @@ -496,6 +496,8 @@ def __init__( self.metadata = metadata self.credentials: SyftSigningKey | None = credentials self._api = api + # TODO + self.services: APIModule | None = None self.communication_protocol: int | str | None = None self.current_protocol: int | str | None = None @@ -958,6 +960,7 @@ def refresh_callback() -> SyftAPI: api=_api, ) self._api = _api + self.services = _api.services return _api From e8933eadaae060f4f2daa0188ecd64c6632a39c6 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 7 May 2024 09:26:53 +0530 Subject: [PATCH 015/114] remove frontend end to end tests --- .github/workflows/pr-tests-frontend.yml | 105 ------------------------ tox.ini | 52 ------------ 2 files changed, 157 deletions(-) diff --git a/.github/workflows/pr-tests-frontend.yml b/.github/workflows/pr-tests-frontend.yml index 729f3794555..7d669b61da8 100644 --- a/.github/workflows/pr-tests-frontend.yml +++ b/.github/workflows/pr-tests-frontend.yml @@ -89,108 +89,3 @@ jobs: DOCKER: true run: | tox -e frontend.test.unit - - pr-tests-frontend-e2e: - strategy: - max-parallel: 3 - matrix: - os: [ubuntu-latest] - python-version: ["3.12"] - - # bug on ubuntu 22 microsoft apt error - runs-on: ubuntu-20.04 # ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - # free 10GB of space - - name: Remove unnecessary files - if: matrix.os == 'ubuntu-latest' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - docker image prune --all --force - docker builder prune --all --force - docker system prune --all --force - - - name: Check for file changes - uses: dorny/paths-filter@v3 - id: changes - with: - base: ${{ github.ref }} - token: ${{ github.token }} - filters: .github/file-filters.yml - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - if: steps.changes.outputs.stack == 'true' - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - if: steps.changes.outputs.stack == 'true' - run: | - pip install --upgrade pip uv==0.1.35 - uv --version - - - name: Get pip cache dir - id: pip-cache - if: steps.changes.outputs.stack == 'true' - shell: bash - run: | - echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT - - - name: pip cache - uses: actions/cache@v4 - if: steps.changes.outputs.stack == 'true' - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-uv-py${{ matrix.python-version }}-${{ hashFiles('packages/hagrid/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-uv-py${{ matrix.python-version }}- - - - name: Install Docker Compose - if: steps.changes.outputs.stack == 'true' && runner.os == 'Linux' - shell: bash - run: | - mkdir -p ~/.docker/cli-plugins - DOCKER_COMPOSE_VERSION=v2.21.0 - curl -sSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose - chmod +x ~/.docker/cli-plugins/docker-compose - - - name: Docker on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-latest' - uses: crazy-max/ghaction-setup-docker@v3.1.0 - - - name: Install Tox - if: steps.changes.outputs.stack == 'true' - run: | - pip install --upgrade tox tox-uv==1.5.1 - - - name: Remove existing containers - if: steps.changes.outputs.stack == 'true' - continue-on-error: true - shell: bash - run: | - docker rm $(docker ps -aq) --force || true - docker volume prune -f || true - docker buildx use default || true - - - uses: pnpm/action-setup@v3 - with: - version: 8 - run_install: false - dest: ~/setup-pnpm - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - # cache: "pnpm" - # cache-dependency-path: packages/grid/frontend/pnpm-lock.yaml - - - name: Run Frontend Playwright e2e Tests - if: steps.changes.outputs.stack == 'true' - env: - DOCKER: true - run: | - tox -e frontend.test.e2e diff --git a/tox.ini b/tox.ini index dccce32f8ee..58516669975 100644 --- a/tox.ini +++ b/tox.ini @@ -201,58 +201,6 @@ commands = docker run -t ui-test; \ fi' -[testenv:frontend.test.e2e] -description = Frontend Unit Tests -deps = - {[testenv:hagrid]deps} -allowlist_externals = - docker - bash - pnpm - sleep -passenv=HOME, USER -changedir = {toxinidir}/packages/grid/frontend -setenv = - HAGRID_FLAGS = {env:HAGRID_FLAGS:--tag=local --test} - ENABLE_SIGNUP=True -commands = - bash ./scripts/check_pnpm.sh - - bash -c "echo Running with HAGRID_FLAGS=$HAGRID_FLAGS; date" - - ; install hagrid - bash -c 'if [[ "$HAGRID_FLAGS" == *"local"* ]]; then \ - uv pip install -e "../../hagrid"; \ - else \ - uv pip install --force hagrid; \ - fi' - - ; fix windows encoding - - chcp 65001 - - ; check docker versions - bash -c "docker --version" - bash -c "docker compose version" - - ; reset volumes and create nodes - bash -c "echo Starting Nodes; date" - bash -c "docker rm -f $(docker ps -a -q) || true" - bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' - - bash -c 'HAGRID_ART=$HAGRID_ART hagrid launch test_domain_1 domain to docker:9081 $HAGRID_FLAGS --enable-signup --no-health-checks --verbose --no-warnings' - - bash -c '(docker logs test_domain_1-frontend-1 -f &) | grep -q -E "Network:\s+https?://[a-zA-Z0-9.-]+:[0-9]+/" || true' - bash -c '(docker logs test_domain_1-backend-1 -f &) | grep -q "Application startup complete" || true' - - pnpm install - pnpm dlx playwright@1.36.1 install --with-deps - pnpm exec playwright install - pnpm test:e2e - - ; shutdown - bash -c "echo Killing Nodes; date" - bash -c 'HAGRID_ART=false hagrid land all --force' - bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' [testenv:stack.test.integration] From 78a25992fd6612dfd95b369e1ce5e25a34d35f92 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 7 May 2024 12:37:17 +0530 Subject: [PATCH 016/114] seggregated k8s tests to different tox task for notebook and integration --- tox.ini | 174 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 111 insertions(+), 63 deletions(-) diff --git a/tox.ini b/tox.ini index dccce32f8ee..f03d3a62b00 100644 --- a/tox.ini +++ b/tox.ini @@ -634,11 +634,10 @@ disable_error_code = attr-defined, valid-type, no-untyped-call, arg-type [testenv:stack.test.integration.k8s] -description = Integration Tests for Core Stack +description = Integration Tests for Core Stack using K8s basepython = python3 deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake changedir = {toxinidir} passenv=HOME, USER @@ -653,41 +652,38 @@ allowlist_externals = echo tox setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} NODE_PORT = {env:NODE_PORT:9082} GITHUB_CI = {env:GITHUB_CI:false} - PYTEST_MODULES = {env:PYTEST_MODULES:frontend container_workload local} - SYFT_BASE_IMAGE_REGISTRY = {env:SYFT_BASE_IMAGE_REGISTRY:k3d-registry.localhost:5800} + PYTEST_MODULES = {env:PYTEST_MODULES:frontend network local_node} + DOMAIN_CLUSTER_NAME = {env:DOMAIN_CLUSTER_NAME:test-domain-1} + GATEWAY_CLUSTER_NAME = {env:GATEWAY_CLUSTER_NAME:test-gateway-1} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} commands = bash -c "echo Running with GITHUB_CI=$GITHUB_CI; date" python -c 'import syft as sy; sy.stage_protocol_changes()' k3d version - # Since cluster name cannot have underscore and environment variable cannot have hyphen - # we are passing a grouped name for node names - # bash -c "docker rm $(docker ps -aq) --force || true" - # Deleting current cluster - bash -c "k3d cluster delete testgateway1 || true" - bash -c "k3d cluster delete testdomain1 || true" + # Deleting Old Cluster + bash -c "k3d cluster delete ${DOMAIN_CLUSTER_NAME} || true" + bash -c "k3d cluster delete ${GATEWAY_CLUSTER_NAME} || true" # Deleting registry & volumes bash -c "k3d registry delete k3d-registry.localhost || true" - bash -c "docker volume rm k3d-testgateway1-images --force || true" - bash -c "docker volume rm k3d-testdomain1-images --force || true" + bash -c "docker volume rm k3d-${DOMAIN_CLUSTER_NAME}-images --force || true" + bash -c "docker volume rm k3d-${GATEWAY_CLUSTER_NAME}-images --force || true" # Create registry tox -e dev.k8s.registry - # Creating testgateway1 cluster on port 9081 + # Creating test-gateway-1 cluster on port 9081 bash -c '\ - export CLUSTER_NAME=testgateway1 CLUSTER_HTTP_PORT=9081 DEVSPACE_PROFILE=gateway && \ + export CLUSTER_NAME=${GATEWAY_CLUSTER_NAME} CLUSTER_HTTP_PORT=9081 DEVSPACE_PROFILE=gateway && \ tox -e dev.k8s.start && \ tox -e dev.k8s.deploy' - # Creating testdomain1 cluster on port 9082 + # Creating test-domain-1 cluster on port 9082 bash -c '\ - export CLUSTER_NAME=testdomain1 CLUSTER_HTTP_PORT=9082 && \ + export CLUSTER_NAME=${DOMAIN_CLUSTER_NAME} CLUSTER_HTTP_PORT=9082 && \ tox -e dev.k8s.start && \ tox -e dev.k8s.deploy' @@ -700,64 +696,116 @@ commands = sleep 30 # wait for front end - bash packages/grid/scripts/wait_for.sh service frontend --context k3d-testdomain1 --namespace syft - bash -c '(kubectl logs service/frontend --context k3d-testdomain1 --namespace syft -f &) | grep -q -E "Network:\s+https?://[a-zA-Z0-9.-]+:[0-9]+/" || true' # wait for test gateway 1 - bash packages/grid/scripts/wait_for.sh service mongo --context k3d-testgateway1 --namespace syft - bash packages/grid/scripts/wait_for.sh service backend --context k3d-testgateway1 --namespace syft - bash packages/grid/scripts/wait_for.sh service proxy --context k3d-testgateway1 --namespace syft + bash packages/grid/scripts/wait_for.sh service mongo --context k3d-{env:GATEWAY_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service backend --context k3d-{env:GATEWAY_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service proxy --context k3d-{env:GATEWAY_CLUSTER_NAME} --namespace syft # wait for test domain 1 - bash packages/grid/scripts/wait_for.sh service mongo --context k3d-testdomain1 --namespace syft - bash packages/grid/scripts/wait_for.sh service backend --context k3d-testdomain1 --namespace syft - bash packages/grid/scripts/wait_for.sh service proxy --context k3d-testdomain1 --namespace syft - bash packages/grid/scripts/wait_for.sh service seaweedfs --context k3d-testdomain1 --namespace syft + bash packages/grid/scripts/wait_for.sh service mongo --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service backend --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service proxy --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service seaweedfs --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service frontend --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash -c '(kubectl logs service/frontend --context k3d-${DOMAIN_CLUSTER_NAME} --namespace syft -f &) | grep -q -E "Network:\s+https?://[a-zA-Z0-9.-]+:[0-9]+/" || true' # Checking logs generated & startup of test-domain 1 - bash -c '(kubectl logs service/backend --context k3d-testdomain1 --namespace syft -f &) | grep -q "Application startup complete" || true' + bash -c '(kubectl logs service/backend --context k3d-${DOMAIN_CLUSTER_NAME} --namespace syft -f &) | grep -q "Application startup complete" || true' # Checking logs generated & startup of testgateway1 - bash -c '(kubectl logs service/backend --context k3d-testgateway1 --namespace syft -f &) | grep -q "Application startup complete" || true' - - # frontend - bash -c 'if [[ "$PYTEST_MODULES" == *"frontend"* ]]; then \ - echo "Starting frontend"; date; \ - pytest tests/integration -m frontend -p no:randomly -k "test_serves_domain_frontend" --co; \ - pytest tests/integration -m frontend -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no -k "test_serves_domain_frontend"; \ - return=$?; \ - echo "Finished frontend"; date; \ - exit $return; \ + bash -c '(kubectl logs service/backend --context k3d-${GATEWAY_CLUSTER_NAME} --namespace syft -f &) | grep -q "Application startup complete" || true' + + # Run Integration Tests + bash -c '\ + PYTEST_MODULES=($PYTEST_MODULES); \ + for i in "${PYTEST_MODULES[@]}"; do \ + echo "Starting test for $i"; date; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ + return=$?; \ + echo "Finished $i"; \ + date; \ + if [[ $return -ne 0 ]]; then \ + exit $return; \ + fi; \ + done' + + # deleting clusters created + bash -c "CLUSTER_NAME=${DOMAIN_CLUSTER_NAME} tox -e dev.k8s.destroy || true" + bash -c "CLUSTER_NAME=${GATEWAY_CLUSTER_NAME} tox -e dev.k8s.destroy || true" + bash -c "k3d registry delete k3d-registry.localhost || true" + bash -c "docker volume rm k3d-${DOMAIN_CLUSTER_NAME}-images --force || true" + bash -c "docker volume rm k3d-${GATEWAY_CLUSTER_NAME}-images --force || true" + +[testenv:stack.test.notebook.k8s] +description = Notebook Tests for Core Stack using K8s +basepython = python3 +deps = + {[testenv:syft]deps} + nbmake +changedir = {toxinidir} +passenv=HOME, USER +allowlist_externals = + devspace + kubectl + grep + sleep + bash + k3d + echo + tox +setenv = + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} + GITHUB_CI = {env:GITHUB_CI:false} + SYFT_BASE_IMAGE_REGISTRY = {env:SYFT_BASE_IMAGE_REGISTRY:k3d-registry.localhost:5800} + DOMAIN_CLUSTER_NAME = {env:DOMAIN_CLUSTER_NAME:test-domain-1} + NODE_PORT = {env:NODE_PORT:8080} +commands = + bash -c "echo Running with GITHUB_CI=$GITHUB_CI; date" + python -c 'import syft as sy; sy.stage_protocol_changes()' + k3d version + + # Deleting Old Cluster + bash -c "k3d cluster delete ${DOMAIN_CLUSTER_NAME} || true" + + # Deleting registry & volumes + bash -c "k3d registry delete k3d-registry.localhost || true" + bash -c "docker volume rm k3d-${DOMAIN_CLUSTER_NAME}-images --force || true" + + # Create registry + tox -e dev.k8s.registry + + + # Creating test-domain-1 cluster on port NODE_PORT + bash -c '\ + export CLUSTER_NAME=${DOMAIN_CLUSTER_NAME} CLUSTER_HTTP_PORT=${NODE_PORT} && \ + tox -e dev.k8s.start && \ + tox -e dev.k8s.deploy' + + # free up build cache after build of images + bash -c 'if [[ "$GITHUB_CI" != "false" ]]; then \ + docker image prune --all --force; \ + docker builder prune --all --force; \ fi' - # Integration + Gateway Connection Tests - # Gateway tests are not run in kuberetes, as currently,it does not have a way to configure - # high/low side warning flag. - bash -c "source ./scripts/get_k8s_secret_ci.sh; \ - pytest tests/integration/network -k 'not test_domain_gateway_user_code' -p no:randomly -vvvv" - - # Shutting down the gateway cluster to free up space, as the - # below code does not require gateway cluster - bash -c "CLUSTER_NAME=testgateway1 tox -e dev.k8s.destroy || true" - bash -c "docker volume rm k3d-testgateway1-images --force || true" - - ; container workload - ; bash -c 'if [[ "$PYTEST_MODULES" == *"container_workload"* ]]; then \ - ; echo "Starting Container Workload test"; date; \ - ; pytest tests/integration -m container_workload -p no:randomly --co; \ - ; pytest tests/integration -m container_workload -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ - ; return=$?; \ - ; echo "Finished container workload"; date; \ - ; exit $return; \ - ; fi' - - bash -c "source ./scripts/get_k8s_secret_ci.sh; \ - pytest --nbmake notebooks/api/0.8 -p no:randomly -k 'not 10-container-images.ipynb' -vvvv --nbmake-timeout=1000" + sleep 30 + + # wait for test-domain-1 + bash packages/grid/scripts/wait_for.sh service mongo --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service backend --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service proxy --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service seaweedfs --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash packages/grid/scripts/wait_for.sh service frontend --context k3d-{env:DOMAIN_CLUSTER_NAME} --namespace syft + bash -c '(kubectl logs service/frontend --context k3d-${DOMAIN_CLUSTER_NAME} --namespace syft -f &) | grep -q -E "Network:\s+https?://[a-zA-Z0-9.-]+:[0-9]+/" || true' + + # Checking logs generated & startup of test-domain 1 + bash -c '(kubectl logs service/backend --context k3d-${DOMAIN_CLUSTER_NAME} --namespace syft -f &) | grep -q "Application startup complete" || true' + + bash -c "pytest --nbmake notebooks/api/0.8 -p no:randomly -k 'not 10-container-images.ipynb' -vvvv --nbmake-timeout=1000" # deleting clusters created - bash -c "CLUSTER_NAME=testdomain1 tox -e dev.k8s.destroy || true" + bash -c "CLUSTER_NAME=${DOMAIN_CLUSTER_NAME} tox -e dev.k8s.destroy || true" bash -c "k3d registry delete k3d-registry.localhost || true" - bash -c "docker rm $(docker ps -aq) --force || true" - bash -c "docker volume rm k3d-testdomain1-images --force || true" + bash -c "docker volume rm k3d-${DOMAIN_CLUSTER_NAME}-images --force || true" [testenv:syft.build.helm] From f25643d503e210d88e8cf111f6bc9fb798d42083 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 7 May 2024 12:46:32 +0530 Subject: [PATCH 017/114] Added new github actions for integration tests --- .github/workflows/pr-tests-stack.yml | 193 +++++++++++++++++++++++---- 1 file changed, 168 insertions(+), 25 deletions(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 32e227f6c3a..78494d8ba87 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -82,15 +82,13 @@ jobs: run: | tox -e backend.test.basecpu - pr-tests-stack-k8s: + pr-tests-integration-k8s: strategy: max-parallel: 99 matrix: - # os: [ubuntu-latest, macos-latest, windows-latest, windows] - # os: [om-ci-16vcpu-ubuntu2204] os: [ubuntu-latest] python-version: ["3.12"] - pytest-modules: ["frontend network"] + pytest-modules: ["frontend network local_node"] fail-fast: false runs-on: ${{matrix.os}} @@ -171,15 +169,6 @@ jobs: chmod +x kubectl sudo install kubectl /usr/local/bin; - - name: Install k9s - if: steps.changes.outputs.stack == 'true' - run: | - # install k9s - wget https://github.com/derailed/k9s/releases/download/v0.32.4/k9s_Linux_amd64.tar.gz - tar -xvf k9s_Linux_amd64.tar.gz - chmod +x k9s - sudo install k9s /usr/local/bin; - - name: Install helm if: steps.changes.outputs.stack == 'true' run: | @@ -188,14 +177,163 @@ jobs: chmod 700 get_helm.sh ./get_helm.sh + - name: Install K3D and Devspace + if: steps.changes.outputs.stack == 'true' + run: | + K3D_VERSION=v5.6.3 + DEVSPACE_VERSION=v6.3.12 + # install k3d + wget https://github.com/k3d-io/k3d/releases/download/${K3D_VERSION}/k3d-linux-amd64 + mv k3d-linux-amd64 k3d + chmod +x k3d + export PATH=`pwd`:$PATH + k3d version + curl -sSL https://github.com/loft-sh/devspace/releases/download/${DEVSPACE_VERSION}/devspace-linux-amd64 -o ./devspace + chmod +x devspace + devspace version + - name: Run K8s & Helm integration tests if: steps.changes.outputs.stack == 'true' timeout-minutes: 60 env: - HAGRID_ART: false PYTEST_MODULES: "${{ matrix.pytest-modules }}" GITHUB_CI: true shell: bash + run: | + tox -e stack.test.integration.k8s + tox -e syft.build.helm + tox -e syft.package.helm + # tox -e syft.test.helm + + - name: Get current timestamp + id: date + if: failure() + shell: bash + run: echo "date=$(date +%s)" >> $GITHUB_OUTPUT + + - name: Collect logs from k3d + if: steps.changes.outputs.stack == 'true' && failure() + shell: bash + run: | + mkdir -p ./k8s-logs + kubectl describe all -A --context k3d-test-gateway-1 --namespace syft > ./k8s-logs/test-gateway-1-desc-${{ steps.date.outputs.date }}.txt + kubectl describe all -A --context k3d-test-domain-1 --namespace syft > ./k8s-logs/test-domain-1-desc-${{ steps.date.outputs.date }}.txt + kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-test-gateway-1 --namespace syft > ./k8s-logs/test-gateway-1-logs-${{ steps.date.outputs.date }}.txt + kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-test-domain-1 --namespace syft > ./k8s-logs/test-domain-1-logs-${{ steps.date.outputs.date }}.txt + ls -la ./k8s-logs + + - name: Upload logs to GitHub + uses: actions/upload-artifact@master + if: steps.changes.outputs.stack == 'true' && failure() + with: + name: k8s-logs-integration-${{ matrix.os }}-${{ steps.date.outputs.date }} + path: ./k8s-logs/ + + - name: Cleanup k3d + if: steps.changes.outputs.stack == 'true' && failure() + shell: bash + run: | + export PATH=`pwd`:$PATH + k3d cluster delete test-gateway-1 || true + k3d cluster delete test-domain-1 || true + k3d registry delete k3d-registry.localhost || true + + pr-tests-notebook-k8s: + strategy: + max-parallel: 99 + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + fail-fast: false + + runs-on: ${{matrix.os}} + + steps: + - name: Permission to home directory + run: | + sudo chown -R $USER:$USER $HOME + - uses: actions/checkout@v4 + - name: Check for file changes + uses: dorny/paths-filter@v3 + id: changes + with: + base: ${{ github.ref }} + token: ${{ github.token }} + filters: .github/file-filters.yml + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + if: steps.changes.outputs.stack == 'true' + with: + python-version: ${{ matrix.python-version }} + + - name: Add K3d Registry + run: | + sudo python ./scripts/patch_hosts.py --add-k3d-registry + + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + large-packages: false + + # free 10GB of space + - name: Remove unnecessary files + if: matrix.os == 'ubuntu-latest' + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + docker image prune --all --force + docker builder prune --all --force + docker system prune --all --force + + - name: Upgrade pip + if: steps.changes.outputs.stack == 'true' + run: | + pip install --upgrade pip uv==0.1.35 + uv --version + + - name: Get pip cache dir + if: steps.changes.outputs.stack == 'true' + id: pip-cache + shell: bash + run: | + echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT + + - name: pip cache + uses: actions/cache@v4 + if: steps.changes.outputs.stack == 'true' + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-uv-py${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-uv-py${{ matrix.python-version }} + + - name: Install tox + if: steps.changes.outputs.stack == 'true' + run: | + pip install --upgrade tox tox-uv==1.5.1 + + - name: Install kubectl + if: steps.changes.outputs.stack == 'true' + run: | + # cleanup apt version + sudo apt remove kubectl || true + # install kubectl 1.27 + curl -LO https://dl.k8s.io/release/v1.27.2/bin/linux/amd64/kubectl + chmod +x kubectl + sudo install kubectl /usr/local/bin; + + - name: Install helm + if: steps.changes.outputs.stack == 'true' + run: | + # install helm + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + chmod 700 get_helm.sh + ./get_helm.sh + + - name: Install K3D and Devspace + if: steps.changes.outputs.stack == 'true' run: | K3D_VERSION=v5.6.3 DEVSPACE_VERSION=v6.3.12 @@ -208,10 +346,15 @@ jobs: curl -sSL https://github.com/loft-sh/devspace/releases/download/${DEVSPACE_VERSION}/devspace-linux-amd64 -o ./devspace chmod +x devspace devspace version - tox -e stack.test.integration.k8s - tox -e syft.build.helm - tox -e syft.package.helm - # tox -e syft.test.helm + + - name: Run K8s & Helm integration tests + if: steps.changes.outputs.stack == 'true' + timeout-minutes: 60 + env: + GITHUB_CI: true + shell: bash + run: | + tox -e stack.test.notebook.k8s - name: Get current timestamp id: date @@ -224,17 +367,17 @@ jobs: shell: bash run: | mkdir -p ./k8s-logs - kubectl describe all -A --context k3d-testgateway1 --namespace syft > ./k8s-logs/testgateway1-desc-${{ steps.date.outputs.date }}.txt - kubectl describe all -A --context k3d-testdomain1 --namespace syft > ./k8s-logs/testdomain1-desc-${{ steps.date.outputs.date }}.txt - kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-testgateway1 --namespace syft > ./k8s-logs/testgateway1-logs-${{ steps.date.outputs.date }}.txt - kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-testdomain1 --namespace syft > ./k8s-logs/testdomain1-logs-${{ steps.date.outputs.date }}.txt + kubectl describe all -A --context k3d-test-gateway-1 --namespace syft > ./k8s-logs/test-gateway-1-desc-${{ steps.date.outputs.date }}.txt + kubectl describe all -A --context k3d-test-domain-1 --namespace syft > ./k8s-logs/test-domain-1-desc-${{ steps.date.outputs.date }}.txt + kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-test-gateway-1 --namespace syft > ./k8s-logs/test-gateway-1-logs-${{ steps.date.outputs.date }}.txt + kubectl logs -l app.kubernetes.io/name!=random --prefix=true --context k3d-test-domain-1 --namespace syft > ./k8s-logs/test-domain-1-logs-${{ steps.date.outputs.date }}.txt ls -la ./k8s-logs - name: Upload logs to GitHub uses: actions/upload-artifact@master if: steps.changes.outputs.stack == 'true' && failure() with: - name: k8s-logs-${{ matrix.os }}-${{ steps.date.outputs.date }} + name: k8s-logs-notebook-${{ matrix.os }}-${{ steps.date.outputs.date }} path: ./k8s-logs/ - name: Cleanup k3d @@ -242,6 +385,6 @@ jobs: shell: bash run: | export PATH=`pwd`:$PATH - k3d cluster delete testgateway1 || true - k3d cluster delete testdomain1 || true + k3d cluster delete test-gateway-1 || true + k3d cluster delete test-domain-1 || true k3d registry delete k3d-registry.localhost || true From 0d160bc57e47fd58e925153e06bea73a10554c0e Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 7 May 2024 12:52:07 +0530 Subject: [PATCH 018/114] Replaced step in github action workflow file --- .github/workflows/pr-tests-stack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 78494d8ba87..065e22e216b 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -347,7 +347,7 @@ jobs: chmod +x devspace devspace version - - name: Run K8s & Helm integration tests + - name: Run Notebooks Tests if: steps.changes.outputs.stack == 'true' timeout-minutes: 60 env: From adc7a39d850e6d9c77331e3b8bb1831f9ad30531 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 7 May 2024 13:00:41 +0530 Subject: [PATCH 019/114] reformatted for path errors temporarily --- .github/workflows/pr-tests-stack.yml | 29 +++++++++++----------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 065e22e216b..7e77908838e 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -177,8 +177,13 @@ jobs: chmod 700 get_helm.sh ./get_helm.sh - - name: Install K3D and Devspace + - name: Run K8s & Helm integration tests if: steps.changes.outputs.stack == 'true' + timeout-minutes: 60 + env: + PYTEST_MODULES: "${{ matrix.pytest-modules }}" + GITHUB_CI: true + shell: bash run: | K3D_VERSION=v5.6.3 DEVSPACE_VERSION=v6.3.12 @@ -192,14 +197,6 @@ jobs: chmod +x devspace devspace version - - name: Run K8s & Helm integration tests - if: steps.changes.outputs.stack == 'true' - timeout-minutes: 60 - env: - PYTEST_MODULES: "${{ matrix.pytest-modules }}" - GITHUB_CI: true - shell: bash - run: | tox -e stack.test.integration.k8s tox -e syft.build.helm tox -e syft.package.helm @@ -332,8 +329,12 @@ jobs: chmod 700 get_helm.sh ./get_helm.sh - - name: Install K3D and Devspace + - name: Run Notebooks Tests if: steps.changes.outputs.stack == 'true' + timeout-minutes: 60 + env: + GITHUB_CI: true + shell: bash run: | K3D_VERSION=v5.6.3 DEVSPACE_VERSION=v6.3.12 @@ -346,14 +347,6 @@ jobs: curl -sSL https://github.com/loft-sh/devspace/releases/download/${DEVSPACE_VERSION}/devspace-linux-amd64 -o ./devspace chmod +x devspace devspace version - - - name: Run Notebooks Tests - if: steps.changes.outputs.stack == 'true' - timeout-minutes: 60 - env: - GITHUB_CI: true - shell: bash - run: | tox -e stack.test.notebook.k8s - name: Get current timestamp From 90d803cdccb4c00b5fe0cad0f90a671a9bb8e253 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 15:16:04 +0530 Subject: [PATCH 020/114] Move Orchestra from hagrid to syft --- packages/hagrid/hagrid/orchestra.py | 629 ------------------ packages/syft/src/syft/__init__.py | 7 +- packages/syft/src/syft/client/deploy.py | 33 +- .../syft/src/syft/client/domain_client.py | 2 +- .../syft/src/syft/client/enclave_client.py | 4 +- packages/syft/src/syft/orchestra.py | 331 +++++++++ packages/syft/src/syft/util/util.py | 8 +- 7 files changed, 342 insertions(+), 672 deletions(-) delete mode 100644 packages/hagrid/hagrid/orchestra.py create mode 100644 packages/syft/src/syft/orchestra.py diff --git a/packages/hagrid/hagrid/orchestra.py b/packages/hagrid/hagrid/orchestra.py deleted file mode 100644 index f4e8f3719be..00000000000 --- a/packages/hagrid/hagrid/orchestra.py +++ /dev/null @@ -1,629 +0,0 @@ -"""Python Level API to launch Docker Containers using Hagrid""" - -# future -from __future__ import annotations - -# stdlib -from collections.abc import Callable -from enum import Enum -import getpass -import inspect -import os -import subprocess # nosec -import sys -from threading import Thread -from typing import Any -from typing import TYPE_CHECKING - -# relative -from .cli import str_to_bool -from .grammar import find_available_port -from .names import random_name -from .util import ImportFromSyft -from .util import NodeSideType -from .util import shell - -DEFAULT_PORT = 8080 -DEFAULT_URL = "http://localhost" -# Gevent used instead of threading module ,as we monkey patch gevent in syft -# and this causes context switch error when we use normal threading in hagrid - -ClientAlias = Any # we don't want to import Client in case it changes - -if TYPE_CHECKING: - NodeType = ImportFromSyft.import_node_type() - - -# Define a function to read and print a stream -def read_stream(stream: subprocess.PIPE) -> None: - while True: - line = stream.readline() - if not line: - break - print(line, end="") - - -def to_snake_case(name: str) -> str: - return name.lower().replace(" ", "_") - - -def get_syft_client() -> Any | None: - try: - # syft absolute - import syft as sy - - return sy - except Exception: # nosec - # print("Please install syft with `pip install syft`") - pass - return None - - -def container_exists(name: str) -> bool: - output = shell(f"docker ps -q -f name='{name}'") - return len(output) > 0 - - -def port_from_container(name: str, deployment_type: DeploymentType) -> int | None: - container_suffix = "" - if deployment_type == DeploymentType.SINGLE_CONTAINER: - container_suffix = "-worker-1" - elif deployment_type == DeploymentType.CONTAINER_STACK: - container_suffix = "-proxy-1" - else: - raise NotImplementedError( - f"port_from_container not implemented for the deployment type:{deployment_type}" - ) - - container_name = name + container_suffix - output = shell(f"docker port {container_name}") - if len(output) > 0: - try: - # 80/tcp -> 0.0.0.0:8080 - lines = output.split("\n") - parts = lines[0].split(":") - port = int(parts[1].strip()) - return port - except Exception: # nosec - return None - return None - - -def container_exists_with(name: str, port: int) -> bool: - output = shell( - f"docker ps -q -f name={name} | xargs -n 1 docker port | grep 0.0.0.0:{port}" - ) - return len(output) > 0 - - -def get_node_type(node_type: str | NodeType | None) -> NodeType | None: - NodeType = ImportFromSyft.import_node_type() - if node_type is None: - node_type = os.environ.get("ORCHESTRA_NODE_TYPE", NodeType.DOMAIN) - try: - return NodeType(node_type) - except ValueError: - print(f"node_type: {node_type} is not a valid NodeType: {NodeType}") - return None - - -def get_deployment_type(deployment_type: str | None) -> DeploymentType | None: - if deployment_type is None: - deployment_type = os.environ.get( - "ORCHESTRA_DEPLOYMENT_TYPE", DeploymentType.PYTHON - ) - - # provide shorthands - if deployment_type == "container": - deployment_type = "container_stack" - - try: - return DeploymentType(deployment_type) - except ValueError: - print( - f"deployment_type: {deployment_type} is not a valid DeploymentType: {DeploymentType}" - ) - return None - - -# Can also be specified by the environment variable -# ORCHESTRA_DEPLOYMENT_TYPE -class DeploymentType(Enum): - PYTHON = "python" - SINGLE_CONTAINER = "single_container" - CONTAINER_STACK = "container_stack" - K8S = "k8s" - PODMAN = "podman" - - -class NodeHandle: - def __init__( - self, - node_type: NodeType, - deployment_type: DeploymentType, - node_side_type: NodeSideType, - name: str, - port: int | None = None, - url: str | None = None, - python_node: Any | None = None, - shutdown: Callable | None = None, - ) -> None: - self.node_type = node_type - self.name = name - self.port = port - self.url = url - self.python_node = python_node - self.shutdown = shutdown - self.deployment_type = deployment_type - self.node_side_type = node_side_type - - @property - def client(self) -> Any: - if self.port: - sy = get_syft_client() - return sy.login_as_guest(url=self.url, port=self.port) # type: ignore - elif self.deployment_type == DeploymentType.PYTHON: - return self.python_node.get_guest_client(verbose=False) # type: ignore - else: - raise NotImplementedError( - f"client not implemented for the deployment type:{self.deployment_type}" - ) - - def login_as_guest(self, **kwargs: Any) -> ClientAlias: - return self.client.login_as_guest(**kwargs) - - def login( - self, email: str | None = None, password: str | None = None, **kwargs: Any - ) -> ClientAlias: - if not email: - email = input("Email: ") - - if not password: - password = getpass.getpass("Password: ") - - return self.client.login(email=email, password=password, **kwargs) - - def register( - self, - name: str, - email: str | None = None, - password: str | None = None, - password_verify: str | None = None, - institution: str | None = None, - website: str | None = None, - ) -> Any: - SyftError = ImportFromSyft.import_syft_error() - if not email: - email = input("Email: ") - if not password: - password = getpass.getpass("Password: ") - if not password_verify: - password_verify = getpass.getpass("Confirm Password: ") - if password != password_verify: - return SyftError(message="Passwords do not match") - - client = self.client - return client.register( - name=name, - email=email, - password=password, - institution=institution, - password_verify=password_verify, - website=website, - ) - - def land(self) -> None: - if self.deployment_type == DeploymentType.PYTHON: - if self.shutdown: - self.shutdown() - else: - Orchestra.land(self.name, deployment_type=self.deployment_type) - - -def deploy_to_python( - node_type_enum: NodeType, - deployment_type_enum: DeploymentType, - port: int | str, - name: str, - host: str, - reset: bool, - tail: bool, - dev_mode: bool, - processes: int, - local_db: bool, - node_side_type: NodeSideType, - enable_warnings: bool, - n_consumers: int, - thread_workers: bool, - create_producer: bool = False, - queue_port: int | None = None, - association_request_auto_approval: bool = False, -) -> NodeHandle | None: - stage_protocol_changes = ImportFromSyft.import_stage_protocol_changes() - NodeType = ImportFromSyft.import_node_type() - sy = get_syft_client() - if sy is None: - return sy - worker_classes = {NodeType.DOMAIN: sy.Domain, NodeType.NETWORK: sy.Gateway} - - # syft >= 0.8.2 - if hasattr(sy, "Enclave"): - worker_classes[NodeType.ENCLAVE] = sy.Enclave - if hasattr(NodeType, "GATEWAY"): - worker_classes[NodeType.GATEWAY] = sy.Gateway - - if dev_mode: - print("Staging Protocol Changes...") - stage_protocol_changes() - - kwargs = { - "name": name, - "host": host, - "port": port, - "reset": reset, - "processes": processes, - "dev_mode": dev_mode, - "tail": tail, - "node_type": node_type_enum, - "node_side_type": node_side_type, - "enable_warnings": enable_warnings, - # new kwargs - "queue_port": queue_port, - "n_consumers": n_consumers, - "create_producer": create_producer, - "association_request_auto_approval": association_request_auto_approval, - } - - if port: - kwargs["in_memory_workers"] = True - if port == "auto": - # dont use default port to prevent port clashes in CI - port = find_available_port(host="localhost", port=None, search=True) - kwargs["port"] = port - - sig = inspect.signature(sy.serve_node) - supported_kwargs = {k: v for k, v in kwargs.items() if k in sig.parameters} - - start, stop = sy.serve_node(**supported_kwargs) - start() - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - port=port, - url="http://localhost", - shutdown=stop, - node_side_type=node_side_type, - ) - else: - kwargs["local_db"] = local_db - kwargs["thread_workers"] = thread_workers - if node_type_enum in worker_classes: - worker_class = worker_classes[node_type_enum] - sig = inspect.signature(worker_class.named) - supported_kwargs = {k: v for k, v in kwargs.items() if k in sig.parameters} - if "node_type" in sig.parameters.keys() and "migrate" in sig.parameters: - supported_kwargs["migrate"] = True - worker = worker_class.named(**supported_kwargs) - else: - raise NotImplementedError(f"node_type: {node_type_enum} is not supported") - - def stop() -> None: - worker.stop() - - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - python_node=worker, - node_side_type=node_side_type, - shutdown=stop, - ) - - -def deploy_to_k8s( - node_type_enum: NodeType, - deployment_type_enum: DeploymentType, - name: str, - node_side_type: NodeSideType, -) -> NodeHandle: - node_port = int(os.environ.get("NODE_PORT", f"{DEFAULT_PORT}")) - node_url = str(os.environ.get("NODE_URL", f"{DEFAULT_URL}")) - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - port=node_port, - url=node_url, - node_side_type=node_side_type, - ) - - -def deploy_to_podman( - node_type_enum: NodeType, - deployment_type_enum: DeploymentType, - name: str, - node_side_type: NodeSideType, -) -> NodeHandle: - node_port = int(os.environ.get("NODE_PORT", f"{DEFAULT_PORT}")) - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - port=node_port, - url="http://localhost", - node_side_type=node_side_type, - ) - - -def deploy_to_container( - node_type_enum: NodeType, - deployment_type_enum: DeploymentType, - node_side_type: NodeSideType, - reset: bool, - cmd: bool, - tail: bool, - verbose: bool, - tag: str, - render: bool, - dev_mode: bool, - port: int | str, - name: str, - enable_warnings: bool, - in_memory_workers: bool, - association_request_auto_approval: bool = False, -) -> NodeHandle | None: - if port == "auto" or port is None: - if container_exists(name=name): - port = port_from_container(name=name, deployment_type=deployment_type_enum) # type: ignore - else: - port = find_available_port(host="localhost", port=DEFAULT_PORT, search=True) - - # Currently by default we launch in dev mode - if reset: - Orchestra.reset(name, deployment_type_enum) - else: - if container_exists_with(name=name, port=port): - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - port=port, - url="http://localhost", - node_side_type=node_side_type, - ) - - # Start a subprocess and capture its output - commands = ["hagrid", "launch"] - - name = random_name() if not name else name - commands.extend([name, node_type_enum.value]) - - commands.append("to") - commands.append(f"docker:{port}") - - if dev_mode: - commands.append("--dev") - - if not enable_warnings: - commands.append("--no-warnings") - - if node_side_type.lower() == NodeSideType.LOW_SIDE.value.lower(): - commands.append("--low-side") - - if in_memory_workers: - commands.append("--in-mem-workers") - - # by default , we deploy as container stack - if deployment_type_enum == DeploymentType.SINGLE_CONTAINER: - commands.append("--deployment-type=single_container") - - if association_request_auto_approval: - commands.append("--enable-association-auto-approval") - - if cmd: - commands.append("--cmd") - - if tail: - commands.append("--tail") - - if verbose: - commands.append("--verbose") - - if tag: - commands.append(f"--tag={tag}") - - if render: - commands.append("--render") - - # needed for building containers - USER = os.environ.get("USER", getpass.getuser()) - env = os.environ.copy() - env["USER"] = USER - - process = subprocess.Popen( # nosec - commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env - ) - # Start threads to read and print the output and error streams - stdout_thread = Thread(target=read_stream, args=(process.stdout,)) - stderr_thread = Thread(target=read_stream, args=(process.stderr,)) - # todo, raise errors - stdout_thread.start() - stderr_thread.start() - stdout_thread.join() - stderr_thread.join() - - if not cmd: - return NodeHandle( - node_type=node_type_enum, - deployment_type=deployment_type_enum, - name=name, - port=port, - url="http://localhost", - node_side_type=node_side_type, - ) - return None - - -class Orchestra: - @staticmethod - def launch( - # node information and deployment - name: str | None = None, - node_type: str | NodeType | None = None, - deploy_to: str | None = None, - node_side_type: str | None = None, - # worker related inputs - port: int | str | None = None, - processes: int = 1, # temporary work around for jax in subprocess - local_db: bool = False, - dev_mode: bool = False, - cmd: bool = False, - reset: bool = False, - tail: bool = False, - host: str | None = "0.0.0.0", # nosec - tag: str | None = "latest", - verbose: bool = False, - render: bool = False, - enable_warnings: bool = False, - n_consumers: int = 0, - thread_workers: bool = False, - create_producer: bool = False, - queue_port: int | None = None, - in_memory_workers: bool = True, - association_request_auto_approval: bool = False, - ) -> NodeHandle | None: - NodeType = ImportFromSyft.import_node_type() - os.environ["DEV_MODE"] = str(dev_mode) - if dev_mode is True: - thread_workers = True - - # syft 0.8.1 - if node_type == "python": - node_type = NodeType.DOMAIN - if deploy_to is None: - deploy_to = "python" - - dev_mode = str_to_bool(os.environ.get("DEV_MODE", f"{dev_mode}")) - - node_type_enum: NodeType | None = get_node_type(node_type=node_type) - - node_side_type_enum = ( - NodeSideType.HIGH_SIDE - if node_side_type is None - else NodeSideType(node_side_type) - ) - - deployment_type_enum: DeploymentType | None = get_deployment_type( - deployment_type=deploy_to - ) - if not deployment_type_enum: - return None - - if deployment_type_enum == DeploymentType.PYTHON: - return deploy_to_python( - node_type_enum=node_type_enum, - deployment_type_enum=deployment_type_enum, - port=port, - name=name, - host=host, - reset=reset, - tail=tail, - dev_mode=dev_mode, - processes=processes, - local_db=local_db, - node_side_type=node_side_type_enum, - enable_warnings=enable_warnings, - n_consumers=n_consumers, - thread_workers=thread_workers, - create_producer=create_producer, - queue_port=queue_port, - association_request_auto_approval=association_request_auto_approval, - ) - - elif deployment_type_enum == DeploymentType.K8S: - return deploy_to_k8s( - node_type_enum=node_type_enum, - deployment_type_enum=deployment_type_enum, - name=name, - node_side_type=node_side_type_enum, - ) - - elif ( - deployment_type_enum == DeploymentType.CONTAINER_STACK - or deployment_type_enum == DeploymentType.SINGLE_CONTAINER - ): - return deploy_to_container( - node_type_enum=node_type_enum, - deployment_type_enum=deployment_type_enum, - reset=reset, - cmd=cmd, - tail=tail, - verbose=verbose, - tag=tag, - render=render, - dev_mode=dev_mode, - port=port, - name=name, - node_side_type=node_side_type_enum, - enable_warnings=enable_warnings, - in_memory_workers=in_memory_workers, - association_request_auto_approval=association_request_auto_approval, - ) - elif deployment_type_enum == DeploymentType.PODMAN: - return deploy_to_podman( - node_type_enum=node_type_enum, - deployment_type_enum=deployment_type_enum, - name=name, - node_side_type=node_side_type_enum, - ) - # else: - # print(f"deployment_type: {deployment_type_enum} is not supported") - # return None - - @staticmethod - def land( - name: str, deployment_type: str | DeploymentType, reset: bool = False - ) -> None: - deployment_type_enum = DeploymentType(deployment_type) - Orchestra.shutdown(name=name, deployment_type_enum=deployment_type_enum) - if reset: - Orchestra.reset(name, deployment_type_enum=deployment_type_enum) - - @staticmethod - def shutdown( - name: str, deployment_type_enum: DeploymentType, reset: bool = False - ) -> None: - if deployment_type_enum != DeploymentType.PYTHON: - snake_name = to_snake_case(name) - - if reset: - land_output = shell(f"hagrid land {snake_name} --force --prune-vol") - else: - land_output = shell(f"hagrid land {snake_name} --force") - if "Removed" in land_output: - print(f" βœ… {snake_name} Container Removed") - elif "No resource found to remove for project" in land_output: - print(f" βœ… {snake_name} Container does not exist") - else: - print( - f"❌ Unable to remove container: {snake_name} :{land_output}", - file=sys.stderr, - ) - - @staticmethod - def reset(name: str, deployment_type_enum: DeploymentType) -> None: - if deployment_type_enum == DeploymentType.PYTHON: - sy = get_syft_client() - _ = sy.Worker.named(name=name, processes=1, reset=True) # type: ignore - elif ( - deployment_type_enum == DeploymentType.CONTAINER_STACK - or deployment_type_enum == DeploymentType.SINGLE_CONTAINER - ): - Orchestra.shutdown( - name=name, deployment_type_enum=deployment_type_enum, reset=True - ) - else: - raise NotImplementedError( - f"Reset not implemented for the deployment type:{deployment_type_enum}" - ) diff --git a/packages/syft/src/syft/__init__.py b/packages/syft/src/syft/__init__.py index 6ebbad50708..24a7eb69185 100644 --- a/packages/syft/src/syft/__init__.py +++ b/packages/syft/src/syft/__init__.py @@ -15,7 +15,6 @@ from .client.client import login # noqa: F401 from .client.client import login_as_guest # noqa: F401 from .client.client import register # noqa: F401 -from .client.deploy import Orchestra # noqa: F401 from .client.domain_client import DomainClient # noqa: F401 from .client.gateway_client import GatewayClient # noqa: F401 from .client.registry import DomainRegistry # noqa: F401 @@ -33,6 +32,7 @@ from .node.server import serve_node # noqa: F401 from .node.server import serve_node as bind_worker # noqa: F401 from .node.worker import Worker # noqa: F401 +from .orchestra import Orchestra as orchestra # noqa: F401 from .protocol.data_protocol import bump_protocol_version # noqa: F401 from .protocol.data_protocol import check_or_stage_protocol # noqa: F401 from .protocol.data_protocol import get_data_protocol # noqa: F401 @@ -149,11 +149,6 @@ def _settings() -> UserSettings: return settings -@module_property -def _orchestra() -> Orchestra: - return Orchestra - - @module_property def hello_baby() -> None: print("Hello baby!") diff --git a/packages/syft/src/syft/client/deploy.py b/packages/syft/src/syft/client/deploy.py index bd19895ced5..9c60920b150 100644 --- a/packages/syft/src/syft/client/deploy.py +++ b/packages/syft/src/syft/client/deploy.py @@ -1,33 +1,2 @@ -# stdlib -from typing import Any - # relative -from ..service.response import SyftError - - -class InstallOrchestra: - def launch(self, *args: Any, **kwargs: Any) -> None: - return self.error() - - def error(self) -> Any: - message = "Please install hagrid with `pip install -U hagrid`" - return SyftError(message=message) - - def _repr_html_(self) -> str: - return self.error()._repr_html_() - - -def import_orchestra() -> Any: - try: - # third party - from hagrid import Orchestra - - return Orchestra - - except Exception as e: # nosec - print(e) - pass - return InstallOrchestra() - - -Orchestra = import_orchestra() +from ..orchestra import Orchestra diff --git a/packages/syft/src/syft/client/domain_client.py b/packages/syft/src/syft/client/domain_client.py index 109ae563c56..400af1f7248 100644 --- a/packages/syft/src/syft/client/domain_client.py +++ b/packages/syft/src/syft/client/domain_client.py @@ -8,7 +8,6 @@ from typing import cast # third party -from hagrid.orchestra import NodeHandle from loguru import logger from tqdm import tqdm @@ -43,6 +42,7 @@ if TYPE_CHECKING: # relative + from ..orchestra import NodeHandle from ..service.project.project import Project diff --git a/packages/syft/src/syft/client/enclave_client.py b/packages/syft/src/syft/client/enclave_client.py index e215413c023..cfb262dc422 100644 --- a/packages/syft/src/syft/client/enclave_client.py +++ b/packages/syft/src/syft/client/enclave_client.py @@ -5,9 +5,6 @@ from typing import Any from typing import TYPE_CHECKING -# third party -from hagrid.orchestra import NodeHandle - # relative from ..abstract_node import NodeSideType from ..client.api import APIRegistry @@ -29,6 +26,7 @@ if TYPE_CHECKING: # relative + from ..orchestra import NodeHandle from ..service.code.user_code import SubmitUserCode diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py new file mode 100644 index 00000000000..c77f2d0a87d --- /dev/null +++ b/packages/syft/src/syft/orchestra.py @@ -0,0 +1,331 @@ +"""Python Level API to launch Docker Containers using Hagrid""" + +# future +from __future__ import annotations + +# stdlib +from collections.abc import Callable +from enum import Enum +import getpass +import inspect +import os +import sys +from typing import Any + +# relative +from .abstract_node import NodeSideType +from .abstract_node import NodeType +from .client.client import login_as_guest as sy_login_as_guest +from .node.domain import Domain +from .node.enclave import Enclave +from .node.gateway import Gateway +from .node.server import serve_node +from .node.worker import Worker +from .protocol.data_protocol import stage_protocol_changes +from .service.response import SyftError +from .util.util import find_available_port + +DEFAULT_PORT = 8080 +DEFAULT_URL = "http://localhost" +# Gevent used instead of threading module ,as we monkey patch gevent in syft +# and this causes context switch error when we use normal threading in hagrid + +ClientAlias = Any # we don't want to import Client in case it changes + + +def get_node_type(node_type: str | NodeType | None) -> NodeType | None: + if node_type is None: + node_type = os.environ.get("ORCHESTRA_NODE_TYPE", NodeType.DOMAIN) + try: + return NodeType(node_type) + except ValueError: + print(f"node_type: {node_type} is not a valid NodeType: {NodeType}") + return None + + +def get_deployment_type(deployment_type: str | None) -> DeploymentType | None: + if deployment_type is None: + deployment_type = os.environ.get( + "ORCHESTRA_DEPLOYMENT_TYPE", DeploymentType.PYTHON + ) + + try: + return DeploymentType(deployment_type) + except ValueError: + print( + f"deployment_type: {deployment_type} is not a valid DeploymentType: {DeploymentType}" + ) + return None + + +# Can also be specified by the environment variable +# ORCHESTRA_DEPLOYMENT_TYPE +class DeploymentType(Enum): + PYTHON = "python" + K8S = "k8s" + + +class NodeHandle: + def __init__( + self, + node_type: NodeType, + deployment_type: DeploymentType, + node_side_type: NodeSideType, + name: str, + port: int | None = None, + url: str | None = None, + python_node: Any | None = None, + shutdown: Callable | None = None, + ) -> None: + self.node_type = node_type + self.name = name + self.port = port + self.url = url + self.python_node = python_node + self.shutdown = shutdown + self.deployment_type = deployment_type + self.node_side_type = node_side_type + + @property + def client(self) -> Any: + if self.port: + return sy_login_as_guest(url=self.url, port=self.port) # type: ignore + elif self.deployment_type == DeploymentType.PYTHON: + return self.python_node.get_guest_client(verbose=False) # type: ignore + else: + raise NotImplementedError( + f"client not implemented for the deployment type:{self.deployment_type}" + ) + + def login_as_guest(self, **kwargs: Any) -> ClientAlias: + return self.client.login_as_guest(**kwargs) + + def login( + self, email: str | None = None, password: str | None = None, **kwargs: Any + ) -> ClientAlias: + if not email: + email = input("Email: ") + + if not password: + password = getpass.getpass("Password: ") + + return self.client.login(email=email, password=password, **kwargs) + + def register( + self, + name: str, + email: str | None = None, + password: str | None = None, + password_verify: str | None = None, + institution: str | None = None, + website: str | None = None, + ) -> Any: + if not email: + email = input("Email: ") + if not password: + password = getpass.getpass("Password: ") + if not password_verify: + password_verify = getpass.getpass("Confirm Password: ") + if password != password_verify: + return SyftError(message="Passwords do not match") + + client = self.client + return client.register( + name=name, + email=email, + password=password, + institution=institution, + password_verify=password_verify, + website=website, + ) + + def land(self) -> None: + if self.deployment_type == DeploymentType.PYTHON: + if self.shutdown: + self.shutdown() + else: + print( + f"Shutdown not implemented for the deployment type:{self.deployment_type}", + file=sys.stderr, + ) + + +def deploy_to_python( + node_type_enum: NodeType, + deployment_type_enum: DeploymentType, + port: int | str, + name: str, + host: str, + reset: bool, + tail: bool, + dev_mode: bool, + processes: int, + local_db: bool, + node_side_type: NodeSideType, + enable_warnings: bool, + n_consumers: int, + thread_workers: bool, + create_producer: bool = False, + queue_port: int | None = None, + association_request_auto_approval: bool = False, +) -> NodeHandle | None: + worker_classes = { + NodeType.DOMAIN: Domain, + NodeType.GATEWAY: Gateway, + NodeType.ENCLAVE: Enclave, + } + + if dev_mode: + print("Staging Protocol Changes...") + stage_protocol_changes() + + kwargs = { + "name": name, + "host": host, + "port": port, + "reset": reset, + "processes": processes, + "dev_mode": dev_mode, + "tail": tail, + "node_type": node_type_enum, + "node_side_type": node_side_type, + "enable_warnings": enable_warnings, + "queue_port": queue_port, + "n_consumers": n_consumers, + "create_producer": create_producer, + "association_request_auto_approval": association_request_auto_approval, + } + + if port: + kwargs["in_memory_workers"] = True + if port == "auto": + # dont use default port to prevent port clashes in CI + port = find_available_port(host="localhost", port=None, search=True) + kwargs["port"] = port + + sig = inspect.signature(serve_node) + supported_kwargs = {k: v for k, v in kwargs.items() if k in sig.parameters} + + start, stop = serve_node(**supported_kwargs) + start() + return NodeHandle( + node_type=node_type_enum, + deployment_type=deployment_type_enum, + name=name, + port=port, + url="http://localhost", + shutdown=stop, + node_side_type=node_side_type, + ) + else: + kwargs["local_db"] = local_db + kwargs["thread_workers"] = thread_workers + if node_type_enum in worker_classes: + worker_class = worker_classes[node_type_enum] + sig = inspect.signature(worker_class.named) + supported_kwargs = {k: v for k, v in kwargs.items() if k in sig.parameters} + if "node_type" in sig.parameters.keys() and "migrate" in sig.parameters: + supported_kwargs["migrate"] = True + worker = worker_class.named(**supported_kwargs) + else: + raise NotImplementedError(f"node_type: {node_type_enum} is not supported") + + def stop() -> None: + worker.stop() + + return NodeHandle( + node_type=node_type_enum, + deployment_type=deployment_type_enum, + name=name, + python_node=worker, + node_side_type=node_side_type, + shutdown=stop, + ) + + +def deploy_to_k8s( + node_type_enum: NodeType, + deployment_type_enum: DeploymentType, + name: str, + node_side_type: NodeSideType, +) -> NodeHandle: + node_port = int(os.environ.get("NODE_PORT", f"{DEFAULT_PORT}")) + node_url = str(os.environ.get("NODE_URL", f"{DEFAULT_URL}")) + return NodeHandle( + node_type=node_type_enum, + deployment_type=deployment_type_enum, + name=name, + port=node_port, + url=node_url, + node_side_type=node_side_type, + ) + + +class Orchestra: + @staticmethod + def launch( + # node information and deployment + name: str | None = None, + node_type: str | NodeType | None = None, + deploy_to: str | None = None, + node_side_type: str | None = None, + # worker related inputs + port: int | str | None = None, + processes: int = 1, # temporary work around for jax in subprocess + local_db: bool = False, + dev_mode: bool = False, + reset: bool = False, + tail: bool = False, + host: str | None = "0.0.0.0", # nosec + enable_warnings: bool = False, + n_consumers: int = 0, + thread_workers: bool = False, + create_producer: bool = False, + queue_port: int | None = None, + association_request_auto_approval: bool = False, + ) -> NodeHandle | None: + if dev_mode is True: + thread_workers = True + os.environ["DEV_MODE"] = str(dev_mode) + + node_type_enum: NodeType | None = get_node_type(node_type=node_type) + node_side_type_enum = ( + NodeSideType.HIGH_SIDE + if node_side_type is None + else NodeSideType(node_side_type) + ) + + deployment_type_enum: DeploymentType | None = get_deployment_type( + deployment_type=deploy_to + ) + + if deployment_type_enum == DeploymentType.PYTHON: + return deploy_to_python( + node_type_enum=node_type_enum, + deployment_type_enum=deployment_type_enum, + port=port, + name=name, + host=host, + reset=reset, + tail=tail, + dev_mode=dev_mode, + processes=processes, + local_db=local_db, + node_side_type=node_side_type_enum, + enable_warnings=enable_warnings, + n_consumers=n_consumers, + thread_workers=thread_workers, + create_producer=create_producer, + queue_port=queue_port, + association_request_auto_approval=association_request_auto_approval, + ) + elif deployment_type_enum == DeploymentType.K8S: + return deploy_to_k8s( + node_type_enum=node_type_enum, + deployment_type_enum=deployment_type_enum, + name=name, + node_side_type=node_side_type_enum, + ) + else: + print(f"deployment_type: {deployment_type_enum} is not supported") + return None diff --git a/packages/syft/src/syft/util/util.py b/packages/syft/src/syft/util/util.py index c01017b1bfe..9dcb3f18b9d 100644 --- a/packages/syft/src/syft/util/util.py +++ b/packages/syft/src/syft/util/util.py @@ -19,6 +19,7 @@ import os from pathlib import Path import platform +import random import re from secrets import randbelow import socket @@ -309,7 +310,11 @@ def print_dynamic_log( return (finish, success) -def find_available_port(host: str, port: int, search: bool = False) -> int: +def find_available_port( + host: str, port: int | None = None, search: bool = False +) -> int: + if port is None: + port = random.randint(1500, 65000) # nosec port_available = False while not port_available: try: @@ -324,6 +329,7 @@ def find_available_port(host: str, port: int, search: bool = False) -> int: port += 1 else: break + sock.close() except Exception as e: print(f"Failed to check port {port}. {e}") From 1c05d481e04a5f4d0972b5ac3946dcc62cadca83 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 15:28:13 +0530 Subject: [PATCH 021/114] Remove all hagrid references from syft --- packages/syft/setup.cfg | 1 - packages/syft/src/syft/client/deploy.py | 2 -- packages/syft/src/syft/node/run.py | 15 ++------------- packages/syft/src/syft/orchestra.py | 4 +--- tox.ini | 1 - 5 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 packages/syft/src/syft/client/deploy.py diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index c0622b3a7c0..2b5dc0c4ea2 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -52,7 +52,6 @@ syft = uvicorn[standard]==0.27.1 fastapi==0.110.0 psutil==5.9.8 - hagrid>=0.3 itables==1.7.1 argon2-cffi==23.1.0 matplotlib==3.8.3 diff --git a/packages/syft/src/syft/client/deploy.py b/packages/syft/src/syft/client/deploy.py deleted file mode 100644 index 9c60920b150..00000000000 --- a/packages/syft/src/syft/client/deploy.py +++ /dev/null @@ -1,2 +0,0 @@ -# relative -from ..orchestra import Orchestra diff --git a/packages/syft/src/syft/node/run.py b/packages/syft/src/syft/node/run.py index d82d88c9a97..5d731d48fd5 100644 --- a/packages/syft/src/syft/node/run.py +++ b/packages/syft/src/syft/node/run.py @@ -1,11 +1,9 @@ # stdlib import argparse -# third party -from hagrid.orchestra import NodeHandle - # relative -from ..client.deploy import Orchestra +from ..orchestra import NodeHandle +from ..orchestra import Orchestra def str_to_bool(bool_str: str | None) -> bool: @@ -71,16 +69,8 @@ def run() -> NodeHandle | None: default="True", dest="tail", ) - parser.add_argument( - "--cmd", - help="cmd mode", - type=str, - default="False", - dest="cmd", - ) args = parser.parse_args() - if args.command != "launch": print("syft launch is the only command currently supported") @@ -100,7 +90,6 @@ def run() -> NodeHandle | None: local_db=args.local_db, processes=args.processes, tail=args.tail, - cmd=args.cmd, ) if not args.tail: return node diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index c77f2d0a87d..c1fd86d486a 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -1,4 +1,4 @@ -"""Python Level API to launch Docker Containers using Hagrid""" +"""Python Level API to launch Syft services.""" # future from __future__ import annotations @@ -27,8 +27,6 @@ DEFAULT_PORT = 8080 DEFAULT_URL = "http://localhost" -# Gevent used instead of threading module ,as we monkey patch gevent in syft -# and this causes context switch error when we use normal threading in hagrid ClientAlias = Any # we don't want to import Client in case it changes diff --git a/tox.ini b/tox.ini index dccce32f8ee..595fbb2b156 100644 --- a/tox.ini +++ b/tox.ini @@ -421,7 +421,6 @@ commands = description = Syft Notebook Tests deps = -e{toxinidir}/packages/syft[dev,data_science] - {[testenv:hagrid]deps} nbmake changedir = {toxinidir}/notebooks allowlist_externals = From da67ce5a79e0da0e785c72a8e010bbeac9e8824a Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 15:49:46 +0530 Subject: [PATCH 022/114] Fix syft notebook tests --- notebooks/api/0.8/09-blob-storage.ipynb | 1 - notebooks/api/0.8/10-container-images.ipynb | 4 +--- notebooks/api/0.8/12-custom-api-endpoint.ipynb | 1 - packages/syft/src/syft/service/code/user_code.py | 4 ++-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/notebooks/api/0.8/09-blob-storage.ipynb b/notebooks/api/0.8/09-blob-storage.ipynb index 713fd53f6f4..93491499896 100644 --- a/notebooks/api/0.8/09-blob-storage.ipynb +++ b/notebooks/api/0.8/09-blob-storage.ipynb @@ -36,7 +36,6 @@ "node = sy.orchestra.launch(\n", " name=\"test-domain-1\",\n", " dev_mode=True,\n", - " in_memory_workers=True,\n", " reset=True,\n", " create_producer=True,\n", ")" diff --git a/notebooks/api/0.8/10-container-images.ipynb b/notebooks/api/0.8/10-container-images.ipynb index 597266cb192..d31dbf9a6d9 100644 --- a/notebooks/api/0.8/10-container-images.ipynb +++ b/notebooks/api/0.8/10-container-images.ipynb @@ -51,8 +51,7 @@ "# Disable inmemory worker for container stack\n", "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\n", " \"container_stack\",\n", - ")\n", - "in_memory_workers = not running_as_container" + ")" ] }, { @@ -66,7 +65,6 @@ " name=\"test-domain-1\",\n", " dev_mode=True,\n", " create_producer=True,\n", - " in_memory_workers=in_memory_workers,\n", " reset=True,\n", " port=8081,\n", ")" diff --git a/notebooks/api/0.8/12-custom-api-endpoint.ipynb b/notebooks/api/0.8/12-custom-api-endpoint.ipynb index dd867dc3757..f84ca9c5c3f 100644 --- a/notebooks/api/0.8/12-custom-api-endpoint.ipynb +++ b/notebooks/api/0.8/12-custom-api-endpoint.ipynb @@ -33,7 +33,6 @@ " dev_mode=True,\n", " create_producer=True,\n", " n_consumers=3,\n", - " in_memory_workers=True,\n", " reset=True,\n", " port=8081,\n", ")\n", diff --git a/packages/syft/src/syft/service/code/user_code.py b/packages/syft/src/syft/service/code/user_code.py index cf99a8cc589..81ba861296d 100644 --- a/packages/syft/src/syft/service/code/user_code.py +++ b/packages/syft/src/syft/service/code/user_code.py @@ -809,7 +809,7 @@ def _ephemeral_node_call( **kwargs: Any, ) -> Any: # relative - from ... import _orchestra + from ...orchestra import Orchestra # Right now we only create a number of workers # In the future we might need to have the same pools/images as well @@ -831,7 +831,7 @@ def _ephemeral_node_call( time_alive = 300 # This could be changed given the work on containers - ep_node = _orchestra().launch( + ep_node = Orchestra.launch( name=f"ephemeral_node_{self.func_name}_{random.randint(a=0, b=10000)}", # nosec reset=True, create_producer=True, From fc61bc9cef44ba0246da95ef2a429ba159120318 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 16:14:08 +0530 Subject: [PATCH 023/114] fix tests --- .../data-engineer/02-deployment-types.ipynb | 4 ++-- .../Enclave-single-notebook-high-low-network.ipynb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/notebooks/tutorials/data-engineer/02-deployment-types.ipynb b/notebooks/tutorials/data-engineer/02-deployment-types.ipynb index b4c43e5929d..1bd572a26fe 100644 --- a/notebooks/tutorials/data-engineer/02-deployment-types.ipynb +++ b/notebooks/tutorials/data-engineer/02-deployment-types.ipynb @@ -67,7 +67,7 @@ "metadata": {}, "outputs": [], "source": [ - "memory_node = sy.Orchestra.launch(\n", + "memory_node = sy.orchestra.launch(\n", " name=\"Arbitrary Dev Node\",\n", " dev_mode=True,\n", " reset=True,\n", @@ -99,7 +99,7 @@ "metadata": {}, "outputs": [], "source": [ - "webserver_node = sy.Orchestra.launch(\n", + "webserver_node = sy.orchestra.launch(\n", " name=\"Arbitrary Webserver Dev Node\", dev_mode=True, reset=True, port=8081\n", ")" ] diff --git a/notebooks/tutorials/enclaves/Enclave-single-notebook-high-low-network.ipynb b/notebooks/tutorials/enclaves/Enclave-single-notebook-high-low-network.ipynb index 46c7bd1db3f..d95d906f952 100644 --- a/notebooks/tutorials/enclaves/Enclave-single-notebook-high-low-network.ipynb +++ b/notebooks/tutorials/enclaves/Enclave-single-notebook-high-low-network.ipynb @@ -45,7 +45,7 @@ "metadata": {}, "outputs": [], "source": [ - "embassador_node_low = sy.Orchestra.launch(\n", + "embassador_node_low = sy.orchestra.launch(\n", " name=\"ambassador node\",\n", " node_side_type=\"low\",\n", " local_db=True,\n", @@ -69,14 +69,14 @@ "metadata": {}, "outputs": [], "source": [ - "ca_node_low = sy.Orchestra.launch(\n", + "ca_node_low = sy.orchestra.launch(\n", " name=\"canada-1\",\n", " node_side_type=\"low\",\n", " local_db=True,\n", " reset=True,\n", " # enable_warnings=True,\n", ")\n", - "it_node_low = sy.Orchestra.launch(\n", + "it_node_low = sy.orchestra.launch(\n", " name=\"italy-1\",\n", " node_side_type=\"low\",\n", " local_db=True,\n", @@ -125,13 +125,13 @@ " reset=True,\n", " # enable_warnings=True,\n", ")\n", - "ca_node_high = sy.Orchestra.launch(\n", + "ca_node_high = sy.orchestra.launch(\n", " name=\"canada-2\",\n", " local_db=True,\n", " reset=True,\n", " # enable_warnings=True,\n", ")\n", - "it_node_high = sy.Orchestra.launch(\n", + "it_node_high = sy.orchestra.launch(\n", " name=\"italy-2\",\n", " local_db=True,\n", " reset=True,\n", @@ -1062,7 +1062,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" }, "toc": { "base_numbering": 1, From a6ab5b7820b122b1fca2397cac7841d61eb1cecb Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 16:22:01 +0530 Subject: [PATCH 024/114] Fix type errors --- packages/syft/src/syft/orchestra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index c1fd86d486a..3a5a486cd33 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -166,7 +166,7 @@ def deploy_to_python( create_producer: bool = False, queue_port: int | None = None, association_request_auto_approval: bool = False, -) -> NodeHandle | None: +) -> NodeHandle: worker_classes = { NodeType.DOMAIN: Domain, NodeType.GATEWAY: Gateway, @@ -281,7 +281,7 @@ def launch( create_producer: bool = False, queue_port: int | None = None, association_request_auto_approval: bool = False, - ) -> NodeHandle | None: + ) -> NodeHandle: if dev_mode is True: thread_workers = True os.environ["DEV_MODE"] = str(dev_mode) @@ -324,6 +324,6 @@ def launch( name=name, node_side_type=node_side_type_enum, ) - else: - print(f"deployment_type: {deployment_type_enum} is not supported") - return None + raise NotImplementedError( + f"deployment_type: {deployment_type_enum} is not supported" + ) From bd2ae326a9e159504c32d3c5edbb1164392a9fc9 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 16:33:36 +0530 Subject: [PATCH 025/114] Rename DeploymentType.K8S to DeploymentType.EXTERNAL --- .github/workflows/pr-tests-syft.yml | 2 +- notebooks/admin/Custom API + Custom Worker.ipynb | 4 ++-- notebooks/api/0.8/10-container-images.ipynb | 12 +++++------- notebooks/api/0.8/11-container-images-k8s.ipynb | 2 +- packages/syft/src/syft/orchestra.py | 8 ++++---- tox.ini | 8 ++++---- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-tests-syft.yml b/.github/workflows/pr-tests-syft.yml index cc8fcb00ecb..6e24875c2a1 100644 --- a/.github/workflows/pr-tests-syft.yml +++ b/.github/workflows/pr-tests-syft.yml @@ -202,7 +202,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.10", "3.11", "3.12"] - deployment-type: ["k8s"] + deployment-type: ["external"] notebook-paths: ["api/0.8"] fail-fast: false diff --git a/notebooks/admin/Custom API + Custom Worker.ipynb b/notebooks/admin/Custom API + Custom Worker.ipynb index bbf47476301..dca2e4d1ff3 100644 --- a/notebooks/admin/Custom API + Custom Worker.ipynb +++ b/notebooks/admin/Custom API + Custom Worker.ipynb @@ -36,8 +36,8 @@ "metadata": {}, "outputs": [], "source": [ - "## k8s mode\n", - "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"k8s\"\n", + "## external mode\n", + "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", "# os.environ[\"DEV_MODE\"] = \"True\"\n", "domain_client = sy.login(\n", " email=\"info@openmined.org\",\n", diff --git a/notebooks/api/0.8/10-container-images.ipynb b/notebooks/api/0.8/10-container-images.ipynb index d31dbf9a6d9..eca94f64d55 100644 --- a/notebooks/api/0.8/10-container-images.ipynb +++ b/notebooks/api/0.8/10-container-images.ipynb @@ -43,15 +43,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Uncomment this to run the whole docker based custom workers\n", - "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"container_stack\"\n", + "# Uncomment this to run on single docker containers\n", + "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", "# os.environ[\"DEV_MODE\"] = \"True\"\n", "\n", "\n", - "# Disable inmemory worker for container stack\n", - "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\n", - " \"container_stack\",\n", - ")" + "# Disable inmemory worker for external stack\n", + "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\"external\",)" ] }, { @@ -1480,7 +1478,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/notebooks/api/0.8/11-container-images-k8s.ipynb b/notebooks/api/0.8/11-container-images-k8s.ipynb index c9663acd3ad..7e7351d1ff3 100644 --- a/notebooks/api/0.8/11-container-images-k8s.ipynb +++ b/notebooks/api/0.8/11-container-images-k8s.ipynb @@ -45,7 +45,7 @@ "metadata": {}, "outputs": [], "source": [ - "os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"k8s\"\n", + "os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", "os.environ[\"DEV_MODE\"] = \"True\"\n", "\n", "# Uncomment this to add custom values\n", diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index 3a5a486cd33..e43a6494971 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -60,7 +60,7 @@ def get_deployment_type(deployment_type: str | None) -> DeploymentType | None: # ORCHESTRA_DEPLOYMENT_TYPE class DeploymentType(Enum): PYTHON = "python" - K8S = "k8s" + EXTERNAL = "external" class NodeHandle: @@ -241,7 +241,7 @@ def stop() -> None: ) -def deploy_to_k8s( +def deploy_to_external( node_type_enum: NodeType, deployment_type_enum: DeploymentType, name: str, @@ -317,8 +317,8 @@ def launch( queue_port=queue_port, association_request_auto_approval=association_request_auto_approval, ) - elif deployment_type_enum == DeploymentType.K8S: - return deploy_to_k8s( + elif deployment_type_enum == DeploymentType.EXTERNAL: + return deploy_to_external( node_type_enum=node_type_enum, deployment_type_enum=deployment_type_enum, name=name, diff --git a/tox.ini b/tox.ini index 595fbb2b156..badcf224cea 100644 --- a/tox.ini +++ b/tox.ini @@ -495,7 +495,7 @@ changedir = {toxinidir}/notebooks allowlist_externals = bash setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} DEV_MODE = {env:DEV_MODE:True} TEST_NOTEBOOK_PATHS = {env:TEST_NOTEBOOK_PATHS:api/0.8} ENABLE_SIGNUP=True @@ -652,7 +652,7 @@ allowlist_externals = echo tox setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} NODE_PORT = {env:NODE_PORT:9082} GITHUB_CI = {env:GITHUB_CI:false} PYTEST_MODULES = {env:PYTEST_MODULES:frontend container_workload local} @@ -812,7 +812,7 @@ allowlist_externals = bash tox setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} NODE_PORT = {env:NODE_PORT:8080} NODE_URL = {env:NODE_URL:http://localhost} EXCLUDE_NOTEBOOKS = {env:EXCLUDE_NOTEBOOKS:not 10-container-images.ipynb} @@ -1072,7 +1072,7 @@ allowlist_externals = pytest passenv = EXTERNAL_REGISTRY,EXTERNAL_REGISTRY_USERNAME,EXTERNAL_REGISTRY_PASSWORD setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:k8s} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} NODE_PORT = {env:NODE_PORT:8080} NODE_URL = {env:NODE_URL:http://localhost} EXCLUDE_NOTEBOOKS = {env:EXCLUDE_NOTEBOOKS:} From f4039ea135021a88b7f3b8f657c545834e92f3ed Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 16:42:47 +0530 Subject: [PATCH 026/114] Add rich dependency to syft package --- packages/syft/setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 2b5dc0c4ea2..ef339084776 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -67,6 +67,7 @@ syft = PyYAML==6.0.1 azure-storage-blob==12.19.1 ipywidgets==8.1.2 + rich==13.7.1 install_requires = %(syft)s From b2bcce81e36ad314ed7e63ff68cad45c8887c229 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 18:09:37 +0530 Subject: [PATCH 027/114] Remove hagrid from stack.test.notebook --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 595fbb2b156..aae3fa8b10c 100644 --- a/tox.ini +++ b/tox.ini @@ -502,7 +502,6 @@ setenv = commands = # Volume cleanup - bash -c 'hagrid land all --force || true' bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' bash -c "echo Running with ORCHESTRA_DEPLOYMENT_TYPE=$ORCHESTRA_DEPLOYMENT_TYPE DEV_MODE=$DEV_MODE TEST_NOTEBOOK_PATHS=$TEST_NOTEBOOK_PATHS; date" @@ -516,7 +515,6 @@ commands = ; pytest --nbmake tutorials -p no:randomly -vvvv ; pytest --nbmake tutorials/pandas-cookbook -p no:randomly -vvvv - bash -c 'hagrid land all --force' bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' [testenv:stack.test.vm] From e702be99aae37921f2e61ed2ff6b86f75c138ed9 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 7 May 2024 19:51:59 +0530 Subject: [PATCH 028/114] [syft] wait for seaweedfs to be ready --- packages/syft/setup.cfg | 1 + .../syft/src/syft/store/blob_storage/seaweedfs.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 6ef660e1f19..18fd140c20f 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -68,6 +68,7 @@ syft = PyYAML==6.0.1 azure-storage-blob==12.19.1 ipywidgets==8.1.2 + tenacity==8.3.0 install_requires = %(syft)s diff --git a/packages/syft/src/syft/store/blob_storage/seaweedfs.py b/packages/syft/src/syft/store/blob_storage/seaweedfs.py index 74762c4155f..e31adc18b7d 100644 --- a/packages/syft/src/syft/store/blob_storage/seaweedfs.py +++ b/packages/syft/src/syft/store/blob_storage/seaweedfs.py @@ -11,7 +11,12 @@ from botocore.client import BaseClient as S3BaseClient from botocore.client import ClientError as BotoClientError from botocore.client import Config +from botocore.exceptions import ConnectionError import requests +from tenacity import retry +from tenacity import retry_if_exception_type +from tenacity import stop_after_delay +from tenacity import wait_fixed from tqdm import tqdm from typing_extensions import Self @@ -215,12 +220,22 @@ def __init__( self.default_bucket_name = default_bucket_name self.config = config + self._check_connection() + def __enter__(self) -> Self: return self def __exit__(self, *exc: Any) -> None: self.client.close() + @retry( + wait=wait_fixed(5), + stop=stop_after_delay(60), + retry=retry_if_exception_type(ConnectionError), + ) + def _check_connection(self) -> dict: + return self.client.list_buckets() + def read( self, fp: SecureFilePathLocation, From 755a15e423eaed15f7257ac5187a8d951d4f0a94 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 7 May 2024 20:50:50 +0530 Subject: [PATCH 029/114] [k8s] make registry a bit more reliable on docker macos --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dccce32f8ee..b9cb2a6ba70 100644 --- a/tox.ini +++ b/tox.ini @@ -886,7 +886,8 @@ commands = bash -c 'k3d --version' ; create registry - bash -c 'k3d registry create registry.localhost --port 5800 -v $HOME/.k3d-registry:/var/lib/registry || true' + bash -c 'docker volume create k3d-registry-vol || true' + bash -c 'k3d registry create registry.localhost --port 5800 -v k3d-registry-vol:/var/lib/registry || true' ; add patches to host bash -c 'if ! grep -q k3d-registry.localhost /etc/hosts; then sudo {envpython} scripts/patch_hosts.py --add-k3d-registry --fix-docker-hosts; fi' From 7e4412e6a0e09991c6880d69009483ca9e72cc72 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Tue, 7 May 2024 21:54:01 +0530 Subject: [PATCH 030/114] Rename `external` deployment type to `remote` --- .github/workflows/pr-tests-syft.yml | 2 +- notebooks/admin/Custom API + Custom Worker.ipynb | 4 ++-- notebooks/api/0.8/10-container-images.ipynb | 6 +++--- notebooks/api/0.8/11-container-images-k8s.ipynb | 2 +- packages/syft/src/syft/orchestra.py | 8 ++++---- tox.ini | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pr-tests-syft.yml b/.github/workflows/pr-tests-syft.yml index 6e24875c2a1..6ce6615a138 100644 --- a/.github/workflows/pr-tests-syft.yml +++ b/.github/workflows/pr-tests-syft.yml @@ -202,7 +202,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.10", "3.11", "3.12"] - deployment-type: ["external"] + deployment-type: ["remote"] notebook-paths: ["api/0.8"] fail-fast: false diff --git a/notebooks/admin/Custom API + Custom Worker.ipynb b/notebooks/admin/Custom API + Custom Worker.ipynb index dca2e4d1ff3..ef6bde6ab9c 100644 --- a/notebooks/admin/Custom API + Custom Worker.ipynb +++ b/notebooks/admin/Custom API + Custom Worker.ipynb @@ -36,8 +36,8 @@ "metadata": {}, "outputs": [], "source": [ - "## external mode\n", - "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", + "## remote mode\n", + "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"remote\"\n", "# os.environ[\"DEV_MODE\"] = \"True\"\n", "domain_client = sy.login(\n", " email=\"info@openmined.org\",\n", diff --git a/notebooks/api/0.8/10-container-images.ipynb b/notebooks/api/0.8/10-container-images.ipynb index eca94f64d55..35fbfd926c0 100644 --- a/notebooks/api/0.8/10-container-images.ipynb +++ b/notebooks/api/0.8/10-container-images.ipynb @@ -44,12 +44,12 @@ "outputs": [], "source": [ "# Uncomment this to run on single docker containers\n", - "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", + "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"remote\"\n", "# os.environ[\"DEV_MODE\"] = \"True\"\n", "\n", "\n", - "# Disable inmemory worker for external stack\n", - "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\"external\",)" + "# Disable inmemory worker for remote stack\n", + "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\"remote\",)" ] }, { diff --git a/notebooks/api/0.8/11-container-images-k8s.ipynb b/notebooks/api/0.8/11-container-images-k8s.ipynb index 7e7351d1ff3..77cd71c7912 100644 --- a/notebooks/api/0.8/11-container-images-k8s.ipynb +++ b/notebooks/api/0.8/11-container-images-k8s.ipynb @@ -45,7 +45,7 @@ "metadata": {}, "outputs": [], "source": [ - "os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"external\"\n", + "os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"remote\"\n", "os.environ[\"DEV_MODE\"] = \"True\"\n", "\n", "# Uncomment this to add custom values\n", diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index e43a6494971..ad8a8b92f9d 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -60,7 +60,7 @@ def get_deployment_type(deployment_type: str | None) -> DeploymentType | None: # ORCHESTRA_DEPLOYMENT_TYPE class DeploymentType(Enum): PYTHON = "python" - EXTERNAL = "external" + REMOTE = "remote" class NodeHandle: @@ -241,7 +241,7 @@ def stop() -> None: ) -def deploy_to_external( +def deploy_to_remote( node_type_enum: NodeType, deployment_type_enum: DeploymentType, name: str, @@ -317,8 +317,8 @@ def launch( queue_port=queue_port, association_request_auto_approval=association_request_auto_approval, ) - elif deployment_type_enum == DeploymentType.EXTERNAL: - return deploy_to_external( + elif deployment_type_enum == DeploymentType.REMOTE: + return deploy_to_remote( node_type_enum=node_type_enum, deployment_type_enum=deployment_type_enum, name=name, diff --git a/tox.ini b/tox.ini index f972a3dcfbf..b35ef925475 100644 --- a/tox.ini +++ b/tox.ini @@ -495,7 +495,7 @@ changedir = {toxinidir}/notebooks allowlist_externals = bash setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:remote} DEV_MODE = {env:DEV_MODE:True} TEST_NOTEBOOK_PATHS = {env:TEST_NOTEBOOK_PATHS:api/0.8} ENABLE_SIGNUP=True @@ -650,7 +650,7 @@ allowlist_externals = echo tox setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:remote} NODE_PORT = {env:NODE_PORT:9082} GITHUB_CI = {env:GITHUB_CI:false} PYTEST_MODULES = {env:PYTEST_MODULES:frontend container_workload local} @@ -810,7 +810,7 @@ allowlist_externals = bash tox setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:remote} NODE_PORT = {env:NODE_PORT:8080} NODE_URL = {env:NODE_URL:http://localhost} EXCLUDE_NOTEBOOKS = {env:EXCLUDE_NOTEBOOKS:not 10-container-images.ipynb} @@ -1070,7 +1070,7 @@ allowlist_externals = pytest passenv = EXTERNAL_REGISTRY,EXTERNAL_REGISTRY_USERNAME,EXTERNAL_REGISTRY_PASSWORD setenv = - ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:external} + ORCHESTRA_DEPLOYMENT_TYPE = {env:ORCHESTRA_DEPLOYMENT_TYPE:remote} NODE_PORT = {env:NODE_PORT:8080} NODE_URL = {env:NODE_URL:http://localhost} EXCLUDE_NOTEBOOKS = {env:EXCLUDE_NOTEBOOKS:} From 29b262e39dc2b29a15bb541252c7daf872d8756f Mon Sep 17 00:00:00 2001 From: Koen van der Veen Date: Tue, 7 May 2024 18:51:12 +0200 Subject: [PATCH 031/114] add test to notebook --- .../tutorials/hello-syft/01-hello-syft.ipynb | 52 +++++++++++++++++-- packages/syft/src/syft/__init__.py | 34 ++++++++++++ packages/syft/src/syft/client/api.py | 46 ++++++++++++---- packages/syft/src/syft/client/client.py | 11 ++++ 4 files changed, 129 insertions(+), 14 deletions(-) diff --git a/notebooks/tutorials/hello-syft/01-hello-syft.ipynb b/notebooks/tutorials/hello-syft/01-hello-syft.ipynb index b7354b469b1..d773c3f6f0d 100644 --- a/notebooks/tutorials/hello-syft/01-hello-syft.ipynb +++ b/notebooks/tutorials/hello-syft/01-hello-syft.ipynb @@ -518,6 +518,52 @@ "cell_type": "markdown", "id": "48", "metadata": {}, + "source": [ + "## Final note: autocomplete" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "Earlier in this tutorial, we used services defined on the client, such as `ds_client.code.request_code_execution`. To find out more about the available methods, like `.request_code_execution()`, and services, like `client.code` you can use autocomplete, simply type `ds_client.code.` or `ds_client.services.` for an example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "# autocompletion, but programtic. To test it out, just type client.services. instead in a new cell\n", + "autocompleter = get_ipython().Completer\n", + "_, completions1 = autocompleter.complete(text=\"ds_client.code.\")\n", + "_, completions2 = autocompleter.complete(text=\"ds_client.services.\")\n", + "_, completions3 = autocompleter.complete(text=\"ds_client.api.services.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "assert all(\n", + " [\n", + " \"ds_client.code.get_all\" in completions1,\n", + " \"ds_client.services.code\" in completions2,\n", + " \"ds_client.api.services.code\" in completions3,\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, "source": [ "Once you are done with this tutorial, you can safely shut down the servers as following," ] @@ -525,7 +571,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -535,7 +581,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "54", "metadata": {}, "outputs": [], "source": [] @@ -557,7 +603,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.2" }, "toc": { "base_numbering": 1, diff --git a/packages/syft/src/syft/__init__.py b/packages/syft/src/syft/__init__.py index 21df23dd912..9257fff1e7f 100644 --- a/packages/syft/src/syft/__init__.py +++ b/packages/syft/src/syft/__init__.py @@ -109,6 +109,40 @@ pass # nosec +try: + # third party + from IPython.core.guarded_eval import EVALUATION_POLICIES + + ipython = get_ipython() # type: ignore + ipython.Completer.evaluation = "limited" + ipython.Completer.use_jedi = False + policy = EVALUATION_POLICIES["limited"] + + # this allow for dynamic attribute getters for autocomplete + policy.allowed_getattr_external.update( + [ + ("syft.client.api", "APIModule"), + ("syft.client.api", "SyftAPI"), + ] + ) + original_can_get_attr = policy.can_get_attr + + def patched_can_get_attr(value: Any, attr: str) -> bool: + attr_name = "__syft_allow_autocomplete__" + + # first check if exist to prevent side effects + if hasattr(value, attr_name) and attr in getattr(value, attr_name, []): + return True + else: + return original_can_get_attr(value, attr) + + # this allows property getters to be used in nested autocomplete + policy.can_get_attr = patched_can_get_attr + +except Exception as e: + print(e) + + def module_property(func: Any) -> Callable: """Decorator to turn module functions into properties. Function names must be prefixed with an underscore.""" diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index 48c300898a2..ff04ae889e0 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -68,17 +68,22 @@ from ..service.job.job_stash import Job -try: - # third party - from IPython.core.guarded_eval import EVALUATION_POLICIES +IPYNB_BACKGROUND_METHODS = set( + [ + "getdoc", + "_partialmethod", + "__name__", + "__code__", + "__wrapped__", + "__custom_documentations__", + "__signature__", + "__defaults__", + "__kwdefaults__", + "__custom_documentations__", + ] +) - ipython = get_ipython() # type: ignore - ipython.Completer.evaluation = "limited" - EVALUATION_POLICIES["limited"].allowed_getattr_external.add( - ("syft.client.api", "APIModule") - ) -except Exception: - pass +IPYNB_BACKGROUND_PREFIXES = ["_ipy", "_repr", "__ipython", "__pydantic"] class APIRegistry: @@ -646,13 +651,23 @@ def __getattr__(self, name: str) -> Any: return object.__getattribute__(self, name) except AttributeError: # if we fail, we refresh the api and try again - if self.refresh_callback is not None: + # however, we dont want this to happen all the time because of ipy magic happening + # in the background + if ( + self.refresh_callback is not None + and name not in IPYNB_BACKGROUND_METHODS + and not any( + name.startswith(prefix) for prefix in IPYNB_BACKGROUND_PREFIXES + ) + ): api = self.refresh_callback() try: + # get current path in the module tree new_current_module = api.services for submodule in self.path.split("."): if submodule != "": new_current_module = getattr(new_current_module, submodule) + # retry getting the attribute, if this fails, we throw an error return object.__getattribute__(new_current_module, name) except AttributeError: pass @@ -819,6 +834,15 @@ class SyftAPI(SyftObject): __user_role: ServiceRole = ServiceRole.NONE communication_protocol: PROTOCOL_TYPE + # informs getattr does not have nasty side effects + __syft_allow_autocomplete__ = ["services"] + + # def _repr_html_(self) -> str: + # return self.services._repr_html_() + + def __dir__(self) -> list[str]: + return ["services"] + @staticmethod def for_user( node: AbstractNode, diff --git a/packages/syft/src/syft/client/client.py b/packages/syft/src/syft/client/client.py index 5bf007599e4..a08d5884b54 100644 --- a/packages/syft/src/syft/client/client.py +++ b/packages/syft/src/syft/client/client.py @@ -485,6 +485,17 @@ class SyftClient: __logged_in_username: str = "" __user_role: ServiceRole = ServiceRole.NONE + # informs getattr does not have nasty side effects + __syft_allow_autocomplete__ = [ + "api", + "code", + "jobs", + "users", + "settings", + "notifications", + "custom_api", + ] + def __init__( self, connection: NodeConnection, From 3a212696b6b565b8fec683a77272f14d7bfb0e3b Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 7 May 2024 22:23:58 +0530 Subject: [PATCH 032/114] [syft] bump backend torch & uv --- packages/grid/backend/backend.dockerfile | 4 ++-- packages/syft/setup.cfg | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 75ef55ec5ba..fdecf9c00da 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,6 +1,6 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.32-r0" -ARG TORCH_VERSION="2.2.2" +ARG UV_VERSION="0.1.39-r0" +ARG TORCH_VERSION="2.3.0" # ==================== [BUILD STEP] Python Dev Base ==================== # FROM cgr.dev/chainguard/wolfi-base as syft_deps diff --git a/packages/syft/setup.cfg b/packages/syft/setup.cfg index 18fd140c20f..1c3a8ebe918 100644 --- a/packages/syft/setup.cfg +++ b/packages/syft/setup.cfg @@ -90,7 +90,8 @@ data_science = evaluate==0.4.1 recordlinkage==0.16 dm-haiku==0.0.10 - torch==2.2.2 # this gets removed in backend.dockerfile so update the version over there as well! + # backend.dockerfile installs torch separately, so update the version over there as well! + torch==2.3.0 dev = %(test_plugins)s From 3700ba5c8308bf1a73204c856e453c13b5cb3190 Mon Sep 17 00:00:00 2001 From: Koen van der Veen Date: Tue, 7 May 2024 19:10:43 +0200 Subject: [PATCH 033/114] remove comments --- packages/syft/src/syft/client/api.py | 3 --- packages/syft/src/syft/client/client.py | 1 - 2 files changed, 4 deletions(-) diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index ff04ae889e0..ae16edcb285 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -837,9 +837,6 @@ class SyftAPI(SyftObject): # informs getattr does not have nasty side effects __syft_allow_autocomplete__ = ["services"] - # def _repr_html_(self) -> str: - # return self.services._repr_html_() - def __dir__(self) -> list[str]: return ["services"] diff --git a/packages/syft/src/syft/client/client.py b/packages/syft/src/syft/client/client.py index a08d5884b54..1e23169991c 100644 --- a/packages/syft/src/syft/client/client.py +++ b/packages/syft/src/syft/client/client.py @@ -507,7 +507,6 @@ def __init__( self.metadata = metadata self.credentials: SyftSigningKey | None = credentials self._api = api - # TODO self.services: APIModule | None = None self.communication_protocol: int | str | None = None self.current_protocol: int | str | None = None From 1464d05d44e4cc9a78df5bf90c38e4512401e3f2 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 7 May 2024 22:57:42 +0530 Subject: [PATCH 034/114] [k8s] remove k3d registry volume --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b9cb2a6ba70..0706a5352f3 100644 --- a/tox.ini +++ b/tox.ini @@ -1021,7 +1021,7 @@ commands = ; destroy registry bash -c 'k3d registry delete registry.localhost || true' - bash -c 'sudo rm -rf ~/.k3d-registry' + bash -c 'docker volume rm k3d-registry-vol --force || true' [testenv:backend.test.basecpu] description = Base CPU Docker Image Test From 4a62333ca2ded571b2d5322509b2d7763784a674 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 7 May 2024 23:02:02 +0530 Subject: [PATCH 035/114] [k8s] tweak destroy command --- tox.ini | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 0706a5352f3..fbee3634c7e 100644 --- a/tox.ini +++ b/tox.ini @@ -887,7 +887,7 @@ commands = ; create registry bash -c 'docker volume create k3d-registry-vol || true' - bash -c 'k3d registry create registry.localhost --port 5800 -v k3d-registry-vol:/var/lib/registry || true' + bash -c 'k3d registry create registry.localhost --port 5800 -v k3d-registry-vol:/var/lib/registry --no-help || true' ; add patches to host bash -c 'if ! grep -q k3d-registry.localhost /etc/hosts; then sudo {envpython} scripts/patch_hosts.py --add-k3d-registry --fix-docker-hosts; fi' @@ -999,9 +999,6 @@ allowlist_externals = tox bash commands = - ; purge deployment and dangling resources - tox -e dev.k8s.cleanup - ; destroy cluster bash -c '\ rm -rf .devspace; echo ""; \ From 886baa84179658e87954a4be5fdb4650b06d6b2c Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 8 May 2024 12:03:11 +0530 Subject: [PATCH 036/114] fix container workload tests --- notebooks/Experimental/Test.ipynb | 3600 +++++++++++++++++ .../container_workload/pool_image_test.py | 107 +- tox.ini | 1 + 3 files changed, 3663 insertions(+), 45 deletions(-) create mode 100644 notebooks/Experimental/Test.ipynb diff --git a/notebooks/Experimental/Test.ipynb b/notebooks/Experimental/Test.ipynb new file mode 100644 index 00000000000..c766818d73f --- /dev/null +++ b/notebooks/Experimental/Test.ipynb @@ -0,0 +1,3600 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "adc7a8fc-fad9-4703-b918-e0145fb324cb", + "metadata": {}, + "outputs": [], + "source": [ + "# stdlib\n", + "import os\n", + "\n", + "# third party\n", + "import requests\n", + "\n", + "# syft absolute\n", + "import syft as sy\n", + "from syft.client.domain_client import DomainClient\n", + "from syft.custom_worker.config import DockerWorkerConfig\n", + "from syft.service.request.request import Request\n", + "from syft.service.response import SyftSuccess\n", + "from syft.service.worker.worker_image import SyftWorkerImage\n", + "from syft.service.worker.worker_pool import SyftWorker\n", + "from syft.service.worker.worker_pool import WorkerPool" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "46da9304-1f02-453d-9caf-c5ab00f0d469", + "metadata": {}, + "outputs": [], + "source": [ + "registry = \"k3d-registry.localhost:5800\"\n", + "repo = \"openmined/grid-backend\"\n", + "\n", + "if \"k3d\" in registry:\n", + " res = requests.get(url=f\"http://{registry}/v2/{repo}/tags/list\")\n", + " tag = res.json()[\"tags\"][0]\n", + "else:\n", + " tag = sy.__version__\n", + "\n", + "external_registry = os.getenv(\"EXTERNAL_REGISTRY\", registry)\n", + "external_registry_username = os.getenv(\"EXTERNAL_REGISTRY_USERNAME\", None)\n", + "external_registry_password = os.getenv(\"EXTERNAL_REGISTRY_PASSWORD\", None)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b9714331-a33b-4008-8795-8cfd98dfdc92", + "metadata": {}, + "outputs": [], + "source": [ + "def test():\n", + " domain_client: DomainClient = sy.login(\n", + " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", + " )\n", + " image_registry_list = domain_client.api.services.image_registry.get_all()\n", + " if len(image_registry_list) > 1:\n", + " raise Exception(\"Only one registry should be present for testing\")\n", + "\n", + " elif len(image_registry_list) == 1:\n", + " assert (\n", + " image_registry_list[0].url == external_registry\n", + " ), \"External registry different from the one set in the environment variable\"\n", + " return image_registry_list[0].id\n", + " else:\n", + " registry_add_result = domain_client.api.services.image_registry.add(\n", + " external_registry\n", + " )\n", + "\n", + " assert isinstance(registry_add_result, sy.SyftSuccess), str(registry_add_result)\n", + "\n", + " image_registry_list = domain_client.api.services.image_registry.get_all()\n", + " return image_registry_list[0].id" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7d5e989c-80e2-45a1-9c6b-ef4d3e1eafe4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "domain_client: DomainClient = sy.login(\n", + " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6c2cb495-d6bb-4956-a1d0-54daf2a59282", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "

SyftImageRegistry List

\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "\n", + "

0

\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "[SyftImageRegistry(url=k3d-registry.localhost:5800)]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "domain_client.api.services.image_registry.get_all()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9acd6fbe-b94e-4fcf-b8ab-2d4fbbedc1e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test() == test()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4c4620ba-e890-4fd6-835b-6b90a94fd01c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "domain_client = sy.login(port=9082, email=\"info@openmined.org\", password=\"changethis\")\n", + "\n", + "# Submit Docker Worker Config\n", + "docker_config_rl = f\"\"\"\n", + " FROM {registry}/{repo}:{tag}\n", + " RUN pip install recordlinkage\n", + "\"\"\"\n", + "docker_config = DockerWorkerConfig(dockerfile=docker_config_rl)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a966b192-35d8-450b-98ed-8d65cb651924", + "metadata": {}, + "outputs": [], + "source": [ + "# Submit Worker Image\n", + "submit_result = domain_client.api.services.worker_image.submit_dockerfile(\n", + " docker_config=docker_config\n", + ")\n", + "assert isinstance(submit_result, SyftSuccess)\n", + "assert len(domain_client.images.get_all()) == 2" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "286a2155-329d-49d8-9f65-72a701194ddd", + "metadata": {}, + "outputs": [], + "source": [ + "# Validate if we can get the worker image object from its config\n", + "workerimage = domain_client.api.services.worker_image.get_by_config(docker_config)\n", + "assert not isinstance(workerimage, sy.SyftError)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c10f9759-0a57-4b58-8877-b5a16db50959", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Build docker image\n", + "docker_tag = \"openmined/custom-worker-rl:latest\"\n", + "docker_build_result = domain_client.api.services.worker_image.build(\n", + " image_uid=workerimage.id,\n", + " tag=docker_tag,\n", + " registry_uid=test(),\n", + ")\n", + "assert isinstance(docker_build_result, SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b6134199-4471-4cca-96f0-2b82790b1e6c", + "metadata": {}, + "outputs": [], + "source": [ + "# Refresh the worker image object\n", + "workerimage = domain_client.images.get_by_uid(workerimage.id)\n", + "assert not isinstance(workerimage, sy.SyftSuccess)\n", + "\n", + "assert workerimage.is_built\n", + "assert workerimage.image_identifier is not None\n", + "assert workerimage.image_identifier.repo_with_tag == docker_tag\n", + "assert workerimage.image_hash is not None" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2ed3b344-4530-45ea-ba32-5c695449df85", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "domain_client: DomainClient = sy.login(\n", + " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", + ")\n", + "assert len(domain_client.worker_pools.get_all()) == 1" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "9400b08e-295a-47c5-a63c-a0ac0f2249a8", + "metadata": {}, + "outputs": [], + "source": [ + "# Submit Docker Worker Config\n", + "docker_config_opendp = f\"\"\"\n", + " FROM {registry}/{repo}:{tag}\n", + " RUN pip install opendp\n", + "\"\"\"\n", + "docker_config = DockerWorkerConfig(dockerfile=docker_config_opendp)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0366da62-29d4-4018-9849-c327f6d27fb5", + "metadata": {}, + "outputs": [], + "source": [ + "# Submit Worker Image\n", + "submit_result = domain_client.api.services.worker_image.submit_dockerfile(\n", + " docker_config=docker_config\n", + ")\n", + "assert isinstance(submit_result, SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8a1abb0b-65fd-4af7-85eb-ae505e34b572", + "metadata": {}, + "outputs": [], + "source": [ + "worker_image = domain_client.api.services.worker_image.get_by_config(docker_config)\n", + "assert not isinstance(worker_image, sy.SyftError)\n", + "assert worker_image is not None\n", + "assert not worker_image.is_built" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "07d107b2-fc54-4d93-b226-70e8349b7263", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Build docker image\n", + "docker_tag = \"openmined/custom-worker-opendp:latest\"\n", + "docker_build_result = domain_client.api.services.worker_image.build(\n", + " image_uid=worker_image.id, tag=docker_tag, registry_uid=test()\n", + ")\n", + "assert isinstance(docker_build_result, SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3bedaed8-8a6e-4e16-9e99-20a8368ac29b", + "metadata": {}, + "outputs": [], + "source": [ + "push_result = None\n", + "push_result = domain_client.api.services.worker_image.push(\n", + " worker_image.id,\n", + " username=external_registry_username,\n", + " password=external_registry_password,\n", + ")\n", + "assert isinstance(push_result, sy.SyftSuccess), str(push_result)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4bb00397-2540-4fa1-866d-f7f177a79bcc", + "metadata": {}, + "outputs": [], + "source": [ + "# Launch a worker pool\n", + "worker_pool_name = \"custom-worker-pool-ver-1\"\n", + "worker_pool_res = domain_client.api.services.worker_pool.launch(\n", + " name=worker_pool_name,\n", + " image_uid=worker_image.id,\n", + " num_workers=3,\n", + ")\n", + "assert len(worker_pool_res) == 3\n", + "\n", + "assert all(worker.error is None for worker in worker_pool_res)\n", + "assert len(domain_client.worker_pools.get_all()) == 2" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "549f8009-d6b4-41e4-87ae-b20709c38459", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "

ContainerSpawnStatus List

\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "\n", + "

0

\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n" + ], + "text/plain": [ + "[ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-0', worker=syft.service.worker.worker_pool.SyftWorker, error=None),\n", + " ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-1', worker=syft.service.worker.worker_pool.SyftWorker, error=None),\n", + " ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-2', worker=syft.service.worker.worker_pool.SyftWorker, error=None)]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "worker_pool_res" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "1bd2608c-cb1d-403c-886b-041d530295e2", + "metadata": {}, + "outputs": [], + "source": [ + "worker_pool = domain_client.worker_pools[worker_pool_name]\n", + "assert len(worker_pool.worker_list) == 3\n", + "\n", + "workers = worker_pool.workers\n", + "assert len(workers) == 3\n", + "\n", + "for worker in workers:\n", + " assert worker.worker_pool_name == worker_pool_name\n", + " assert worker.image.id == worker_image.id\n", + "\n", + "assert len(worker_pool.healthy_workers) == 3\n", + "\n", + "# Grab the first worker\n", + "first_worker = workers[0]\n", + "\n", + "# Check worker Logs\n", + "logs = domain_client.api.services.worker.logs(uid=first_worker.id)\n", + "assert not isinstance(logs, sy.SyftError)\n", + "\n", + "# Check for worker status\n", + "status_res = domain_client.api.services.worker.status(uid=first_worker.id)\n", + "assert not isinstance(status_res, sy.SyftError)\n", + "assert isinstance(status_res, tuple)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "127cb09a-1dee-4103-aa9d-1e170126644d", + "metadata": {}, + "outputs": [], + "source": [ + "# Delete the pool's workers\n", + "for worker in worker_pool.workers:\n", + " res = domain_client.api.services.worker.delete(uid=worker.id, force=True)\n", + " assert isinstance(res, sy.SyftSuccess)\n", + "\n", + "# TODO: delete the launched pool" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "7d06b8e8-cdea-47e7-a5a2-3113fd814f4c", + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[32], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Clean the build images\u001b[39;00m\n\u001b[1;32m 2\u001b[0m delete_result \u001b[38;5;241m=\u001b[39m domain_client\u001b[38;5;241m.\u001b[39mapi\u001b[38;5;241m.\u001b[39mservices\u001b[38;5;241m.\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mremove(uid\u001b[38;5;241m=\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mid)\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(delete_result, sy\u001b[38;5;241m.\u001b[39mSyftSuccess)\n", + "\u001b[0;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "# Clean the build images\n", + "delete_result = domain_client.api.services.worker_image.remove(uid=worker_image.id)\n", + "assert isinstance(delete_result, sy.SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "520e8f10-9447-4ff6-8fe4-57aa2281ad49", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + } + ], + "source": [ + "domain_client: DomainClient = sy.login(\n", + " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", + ")\n", + "\n", + "ds_username = \"sheldon\"\n", + "ds_email = ds_username + \"@example.com\"\n", + "res = domain_client.register(\n", + " name=ds_username,\n", + " email=ds_email,\n", + " password=\"secret_pw\",\n", + " password_verify=\"secret_pw\",\n", + ")\n", + "# assert isinstance(res, SyftSuccess)\n", + "ds_client = sy.login(email=ds_email, password=\"secret_pw\", port=9082)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "758972d7-31d6-48b8-85ee-fe3f4cbb0d41", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logged into as \n" + ] + }, + { + "data": { + "text/html": [ + "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" + ], + "text/plain": [ + "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# the DS makes a request to create an image and a pool based on the image\n", + "docker_config_np = f\"\"\"\n", + " FROM {registry}/{repo}:{tag}\n", + " RUN pip install numpy\n", + "\"\"\"\n", + "docker_config = DockerWorkerConfig(dockerfile=docker_config_np)\n", + "docker_tag = \"openmined/custom-worker-np:latest\"\n", + "worker_pool_name = \"custom-worker-pool-numpy\"\n", + "request = ds_client.api.services.worker_pool.create_image_and_pool_request(\n", + " pool_name=worker_pool_name,\n", + " num_workers=1,\n", + " tag=docker_tag,\n", + " config=docker_config,\n", + " reason=\"I want to do some more cool data science with PySyft and Recordlinkage\",\n", + " registry_uid=test(),\n", + ")\n", + "assert isinstance(request, Request)\n", + "assert len(request.changes) == 2\n", + "assert request.changes[0].config == docker_config\n", + "assert request.changes[1].num_workers == 1\n", + "assert request.changes[1].pool_name == worker_pool_name" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "d7c69138-8275-4715-bf8d-90b95ba02dae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approving request for domain syft-dev-node\n" + ] + } + ], + "source": [ + "# the domain client approve the request, so the image should be built\n", + "# and the worker pool should be launched\n", + "for r in domain_client.requests:\n", + " if r.id == request.id:\n", + " req_result = r.approve()\n", + " break\n", + "assert isinstance(req_result, SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "6433dcec-5ab0-4d6b-ace0-321aa66c5563", + "metadata": {}, + "outputs": [], + "source": [ + "launched_pool = ds_client.api.services.worker_pool.get_by_name(worker_pool_name)\n", + "assert isinstance(launched_pool, WorkerPool)\n", + "assert launched_pool.name == worker_pool_name\n", + "assert len(launched_pool.worker_list) == 1" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "03770eeb-06b1-4df6-ad1f-c4c9d1bb25eb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "
\n", + "

custom-worker-pool-numpy

\n", + "

\n", + " Created on: \n", + " 2024-05-08 06:16:21\n", + "

\n", + "

\n", + " Healthy Workers:\n", + " 1 / 1\n", + "

\n", + "

\n", + " Running Workers:\n", + " 1 / 1\n", + "

\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "

SyftWorker List

\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "\n", + "

0

\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "\n", + " " + ], + "text/markdown": [ + "```python\n", + "class WorkerPool:\n", + " id: str = f63d58bc20454e36ab6eb960a9dbc7e9\n", + " name: str = \"custom-worker-pool-numpy\"\n", + " image: str = syft.service.worker.worker_image.SyftWorkerImage\n", + " max_count: str = 1\n", + " workers: str = [syft.service.worker.worker_pool.SyftWorker]\n", + " created_at: str = 2024-05-08 06:16:21\n", + "\n", + "```" + ], + "text/plain": [ + "syft.service.worker.worker_pool.WorkerPool" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds_client.api.services.worker_pool[2]" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "9b7efed7-ab8b-4151-8ece-da9b1d06c7cf", + "metadata": {}, + "outputs": [], + "source": [ + "worker: SyftWorker = launched_pool.workers[0]\n", + "assert launched_pool.name in worker.name\n", + "assert worker.status.value == \"Running\"\n", + "assert worker.healthcheck.value == \"βœ…\"\n", + "# assert worker.consumer_state.value == \"Idle\"\n", + "assert isinstance(worker.logs, str)\n", + "assert worker.job_id is None" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "0a8f92be-f6df-4874-a892-2bce4cf0308e", + "metadata": {}, + "outputs": [], + "source": [ + "built_image = ds_client.api.services.worker_image.get_by_config(docker_config)\n", + "assert isinstance(built_image, SyftWorkerImage)\n", + "assert built_image.id == launched_pool.image.id\n", + "assert worker.image.id == built_image.id" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "158d88fa-a3e3-4dfb-8758-2adfaa0015ec", + "metadata": {}, + "outputs": [], + "source": [ + "# third party\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "03a1ba9e-1449-41bc-989d-7c3a7beea09c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
SyftSuccess: Syft function 'custom_worker_func' successfully created. To add a code request, please create a project using `project = syft.Project(...)`, then use command `project.create_code_request`.

" + ], + "text/plain": [ + "SyftSuccess: Syft function 'custom_worker_func' successfully created. To add a code request, please create a project using `project = syft.Project(...)`, then use command `project.create_code_request`." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Dataset\n", + "data = np.array([1, 2, 3])\n", + "data_action_obj = sy.ActionObject.from_obj(data)\n", + "data_pointer = domain_client.api.services.action.set(data_action_obj)\n", + "\n", + "# Function\n", + "\n", + "\n", + "@sy.syft_function(\n", + " input_policy=sy.ExactMatch(x=data_pointer),\n", + " output_policy=sy.SingleExecutionExactOutput(),\n", + " worker_pool_name=launched_pool.name,\n", + ")\n", + "def custom_worker_func(x):\n", + " return {\"y\": x + 1}\n", + "\n", + "\n", + "assert custom_worker_func.worker_pool_name == launched_pool.name\n", + "# Request code execution" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "ea53e10b-a9c4-4ccc-8091-bce269a4ce02", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approving request for domain syft-dev-node\n" + ] + } + ], + "source": [ + "code_request = ds_client.code.request_code_execution(custom_worker_func)\n", + "assert isinstance(code_request, Request)\n", + "assert code_request.status.value == 0 # pending\n", + "for r in domain_client.requests:\n", + " if r.id == code_request.id:\n", + " code_req_result = r.approve(approve_nested=True)\n", + " break\n", + "assert isinstance(code_req_result, SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "36bb45d1-fdc3-4dc9-a18e-3514a85ec37c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
SyftWarning: This is a placeholder object, the real data lives on a different node and is not synced.

" + ], + "text/plain": [ + "SyftWarning: This is a placeholder object, the real data lives on a different node and is not synced." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "job = ds_client.code.custom_worker_func(x=data_pointer, blocking=False)\n", + "assert job.status.value == \"created\"\n", + "job.wait()\n", + "assert job.status.value == \"completed\"\n", + "\n", + "job = domain_client.jobs[-1]\n", + "assert job.job_worker_id == worker.id\n", + "\n", + "# Validate the result received from the syft function\n", + "result = job.wait().get()\n", + "result_matches = result[\"y\"] == data + 1\n", + "assert result_matches.all()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "22dbb1a7-483e-454c-8595-1df11d3bc26d", + "metadata": {}, + "outputs": [], + "source": [ + "# Delete the workers of the launched pools\n", + "for worker in launched_pool.workers:\n", + " res = domain_client.api.services.worker.delete(uid=worker.id, force=True)\n", + " assert isinstance(res, sy.SyftSuccess)\n", + "\n", + "# TODO: delete the launched pool" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "ae1e49c4-0589-405f-9c42-902fbfd9efbf", + "metadata": {}, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[60], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Clean the build images\u001b[39;00m\n\u001b[1;32m 3\u001b[0m delete_result \u001b[38;5;241m=\u001b[39m domain_client\u001b[38;5;241m.\u001b[39mapi\u001b[38;5;241m.\u001b[39mservices\u001b[38;5;241m.\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mremove(uid\u001b[38;5;241m=\u001b[39mbuilt_image\u001b[38;5;241m.\u001b[39mid)\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(delete_result, sy\u001b[38;5;241m.\u001b[39mSyftSuccess)\n", + "\u001b[0;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "# Clean the build images\n", + "\n", + "delete_result = domain_client.api.services.worker_image.remove(uid=built_image.id)\n", + "assert isinstance(delete_result, sy.SyftSuccess)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23fcd0e5-a013-4e3c-9210-527d34456707", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/integration/container_workload/pool_image_test.py b/tests/integration/container_workload/pool_image_test.py index d8cb59f8f1f..87a78f67fab 100644 --- a/tests/integration/container_workload/pool_image_test.py +++ b/tests/integration/container_workload/pool_image_test.py @@ -1,38 +1,70 @@ # stdlib import os -from time import sleep # third party from faker import Faker import numpy as np import pytest +import requests # syft absolute import syft as sy from syft.client.domain_client import DomainClient from syft.custom_worker.config import DockerWorkerConfig -from syft.node.node import get_default_worker_tag_by_env from syft.service.request.request import Request from syft.service.response import SyftSuccess from syft.service.worker.worker_image import SyftWorkerImage from syft.service.worker.worker_pool import SyftWorker from syft.service.worker.worker_pool import WorkerPool -SYFT_BASE_TAG = get_default_worker_tag_by_env() -hagrid_flags = os.getenv("HAGRID_FLAGS") -if hagrid_flags: - SYFT_BASE_TAG = get_default_worker_tag_by_env(dev_mode=True) +registry = os.getenv("SYFT_BASE_IMAGE_REGISTRY", "docker.io") +repo = "openmined/grid-backend" + +if "k3d" in registry: + res = requests.get(url=f"http://{registry}/v2/{repo}/tags/list") + tag = res.json()["tags"][0] +else: + tag = sy.__version__ + +external_registry = os.getenv("EXTERNAL_REGISTRY", registry) +external_registry_username = os.getenv("EXTERNAL_REGISTRY_USERNAME", None) +external_registry_password = os.getenv("EXTERNAL_REGISTRY_PASSWORD", None) + + +@pytest.fixture +def external_registry_uid(domain_1_port): + domain_client: DomainClient = sy.login( + port=domain_1_port, email="info@openmined.org", password="changethis" + ) + image_registry_list = domain_client.api.services.image_registry.get_all() + if len(image_registry_list) > 1: + raise Exception("Only one registry should be present for testing") + + elif len(image_registry_list) == 1: + assert ( + image_registry_list[0].url == external_registry + ), "External registry different from the one set in the environment variable" + return image_registry_list[0].id + else: + registry_add_result = domain_client.api.services.image_registry.add( + external_registry + ) + + assert isinstance(registry_add_result, sy.SyftSuccess), str(registry_add_result) + + image_registry_list = domain_client.api.services.image_registry.get_all() + return image_registry_list[0].id @pytest.mark.container_workload -def test_image_build(domain_1_port) -> None: +def test_image_build(domain_1_port, external_registry_uid) -> None: domain_client: DomainClient = sy.login( port=domain_1_port, email="info@openmined.org", password="changethis" ) # Submit Docker Worker Config docker_config_rl = f""" - FROM openmined/grid-backend:{SYFT_BASE_TAG} + FROM {registry}/{repo}:{tag} RUN pip install recordlinkage """ docker_config = DockerWorkerConfig(dockerfile=docker_config_rl) @@ -49,12 +81,11 @@ def test_image_build(domain_1_port) -> None: assert not isinstance(workerimage, sy.SyftError) # Build docker image - tag_version = sy.UID().short() - docker_tag = f"openmined/custom-worker-rl:{tag_version}" + docker_tag = "openmined/custom-worker-rl:latest" docker_build_result = domain_client.api.services.worker_image.build( image_uid=workerimage.id, tag=docker_tag, - pull=False, + registry_uid=external_registry_uid, ) assert isinstance(docker_build_result, SyftSuccess) @@ -67,18 +98,9 @@ def test_image_build(domain_1_port) -> None: assert workerimage.image_identifier.repo_with_tag == docker_tag assert workerimage.image_hash is not None - # Delete image - delete_result = domain_client.api.services.worker_image.remove(uid=workerimage.id) - assert isinstance(delete_result, sy.SyftSuccess) - - # Validate the image is successfully deleted - assert len(domain_client.images.get_all()) == 1 - workerimage = domain_client.images.get_all()[0] - assert workerimage.config != docker_config - @pytest.mark.container_workload -def test_pool_launch(domain_1_port) -> None: +def test_pool_launch(domain_1_port, external_registry_uid) -> None: domain_client: DomainClient = sy.login( port=domain_1_port, email="info@openmined.org", password="changethis" ) @@ -86,7 +108,7 @@ def test_pool_launch(domain_1_port) -> None: # Submit Docker Worker Config docker_config_opendp = f""" - FROM openmined/grid-backend:{SYFT_BASE_TAG} + FROM {registry}/{repo}:{tag} RUN pip install opendp """ docker_config = DockerWorkerConfig(dockerfile=docker_config_opendp) @@ -103,18 +125,25 @@ def test_pool_launch(domain_1_port) -> None: assert not worker_image.is_built # Build docker image - tag_version = sy.UID().short() - docker_tag = f"openmined/custom-worker-opendp:{tag_version}" + docker_tag = "openmined/custom-worker-opendp:latest" docker_build_result = domain_client.api.services.worker_image.build( image_uid=worker_image.id, tag=docker_tag, - pull=False, + registry_uid=external_registry_uid, ) assert isinstance(docker_build_result, SyftSuccess) + # Push Image to External registry + push_result = None + push_result = domain_client.api.services.worker_image.push( + worker_image.id, + username=external_registry_username, + password=external_registry_password, + ) + assert isinstance(push_result, sy.SyftSuccess), str(push_result) + # Launch a worker pool - pool_version = sy.UID().short() - worker_pool_name = f"custom_worker_pool_ver{pool_version}" + worker_pool_name = "custom-worker-pool-opendp" worker_pool_res = domain_client.api.services.worker_pool.launch( name=worker_pool_name, image_uid=worker_image.id, @@ -156,14 +185,9 @@ def test_pool_launch(domain_1_port) -> None: # TODO: delete the launched pool - # Clean the build images - sleep(10) - delete_result = domain_client.api.services.worker_image.remove(uid=worker_image.id) - assert isinstance(delete_result, sy.SyftSuccess) - @pytest.mark.container_workload -def test_pool_image_creation_job_requests(domain_1_port) -> None: +def test_pool_image_creation_job_requests(domain_1_port, external_registry_uid) -> None: """ Test register ds client, ds requests to create an image and pool creation, do approves, then ds creates a function attached to the worker pool, then creates another @@ -187,21 +211,19 @@ def test_pool_image_creation_job_requests(domain_1_port) -> None: # the DS makes a request to create an image and a pool based on the image docker_config_np = f""" - FROM openmined/grid-backend:{SYFT_BASE_TAG} + FROM {registry}/{repo}:{tag} RUN pip install numpy """ docker_config = DockerWorkerConfig(dockerfile=docker_config_np) - tag_version = sy.UID().short() - docker_tag = f"openmined/custom-worker-np:{tag_version}" - pool_version = sy.UID().short() - worker_pool_name = f"custom_worker_pool_ver{pool_version}" + docker_tag = "openmined/custom-worker-np:latest" + worker_pool_name = "custom-worker-pool-numpy" request = ds_client.api.services.worker_pool.create_image_and_pool_request( pool_name=worker_pool_name, num_workers=1, tag=docker_tag, config=docker_config, reason="I want to do some more cool data science with PySyft and Recordlinkage", - pull_image=False, + registry_uid=external_registry_uid, ) assert isinstance(request, Request) assert len(request.changes) == 2 @@ -224,7 +246,7 @@ def test_pool_image_creation_job_requests(domain_1_port) -> None: worker: SyftWorker = launched_pool.workers[0] assert launched_pool.name in worker.name - assert worker.status.value == "Pending" + assert worker.status.value == "Running" assert worker.healthcheck.value == "βœ…" # assert worker.consumer_state.value == "Idle" assert isinstance(worker.logs, str) @@ -279,8 +301,3 @@ def custom_worker_func(x): assert isinstance(res, sy.SyftSuccess) # TODO: delete the launched pool - - # Clean the build images - sleep(10) - delete_result = domain_client.api.services.worker_image.remove(uid=built_image.id) - assert isinstance(delete_result, sy.SyftSuccess) diff --git a/tox.ini b/tox.ini index 58031a473f0..a55be2dc3d6 100644 --- a/tox.ini +++ b/tox.ini @@ -658,6 +658,7 @@ setenv = DOMAIN_CLUSTER_NAME = {env:DOMAIN_CLUSTER_NAME:test-domain-1} GATEWAY_CLUSTER_NAME = {env:GATEWAY_CLUSTER_NAME:test-gateway-1} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} + SYFT_BASE_IMAGE_REGISTRY = {env:SYFT_BASE_IMAGE_REGISTRY:k3d-registry.localhost:5800} commands = bash -c "echo Running with GITHUB_CI=$GITHUB_CI; date" python -c 'import syft as sy; sy.stage_protocol_changes()' From 6218018fa33cd2df874822924a072c0a2035ebc0 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 8 May 2024 12:14:51 +0530 Subject: [PATCH 037/114] re-enabled container workload on integration test suite --- .github/workflows/pr-tests-stack.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 7e77908838e..38a2b7b2c0a 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -88,7 +88,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.12"] - pytest-modules: ["frontend network local_node"] + pytest-modules: ["frontend network local_node container_workload"] fail-fast: false runs-on: ${{matrix.os}} diff --git a/tox.ini b/tox.ini index a55be2dc3d6..52a44039396 100644 --- a/tox.ini +++ b/tox.ini @@ -654,7 +654,7 @@ allowlist_externals = setenv = NODE_PORT = {env:NODE_PORT:9082} GITHUB_CI = {env:GITHUB_CI:false} - PYTEST_MODULES = {env:PYTEST_MODULES:frontend network local_node} + PYTEST_MODULES = {env:PYTEST_MODULES:frontend network local_node container_workload} DOMAIN_CLUSTER_NAME = {env:DOMAIN_CLUSTER_NAME:test-domain-1} GATEWAY_CLUSTER_NAME = {env:GATEWAY_CLUSTER_NAME:test-gateway-1} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} From 55558b220303dd4f187c42c99a6363d177ce6b84 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 8 May 2024 12:20:45 +0530 Subject: [PATCH 038/114] minor change to re-trigger CI --- tests/integration/container_workload/pool_image_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/container_workload/pool_image_test.py b/tests/integration/container_workload/pool_image_test.py index 87a78f67fab..2695fbcb1c7 100644 --- a/tests/integration/container_workload/pool_image_test.py +++ b/tests/integration/container_workload/pool_image_test.py @@ -62,7 +62,7 @@ def test_image_build(domain_1_port, external_registry_uid) -> None: port=domain_1_port, email="info@openmined.org", password="changethis" ) - # Submit Docker Worker Config + # Submit Docker Worker Config. docker_config_rl = f""" FROM {registry}/{repo}:{tag} RUN pip install recordlinkage From 67e3dac1c9b253a3b8f217e81501e24fb594aaea Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 8 May 2024 12:49:52 +0530 Subject: [PATCH 039/114] add azure blob storage env variable for testing --- .github/workflows/pr-tests-stack.yml | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 38a2b7b2c0a..1dbde3b88f2 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -183,6 +183,7 @@ jobs: env: PYTEST_MODULES: "${{ matrix.pytest-modules }}" GITHUB_CI: true + AZURE_BLOB_STORAGE_KEY: "${{ secrets.AZURE_BLOB_STORAGE_KEY }}" shell: bash run: | K3D_VERSION=v5.6.3 diff --git a/tox.ini b/tox.ini index 52a44039396..b565074f1fc 100644 --- a/tox.ini +++ b/tox.ini @@ -640,7 +640,7 @@ deps = {[testenv:syft]deps} nbmake changedir = {toxinidir} -passenv=HOME, USER +passenv=HOME, USER, AZURE_BLOB_STORAGE_KEY allowlist_externals = devspace kubectl From dd1e8bdc19618333f83effa8ce626b59f3d427af Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 8 May 2024 13:54:22 +0530 Subject: [PATCH 040/114] update uv --- packages/grid/backend/backend.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index fdecf9c00da..bdfedab9527 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -1,5 +1,5 @@ ARG PYTHON_VERSION="3.12" -ARG UV_VERSION="0.1.39-r0" +ARG UV_VERSION="0.1.41-r0" ARG TORCH_VERSION="2.3.0" # ==================== [BUILD STEP] Python Dev Base ==================== # @@ -19,7 +19,7 @@ ENV UV_HTTP_TIMEOUT=600 # keep static deps separate to have each layer cached independently # if amd64 then we need to append +cpu to the torch version -# limitation of uv - https://github.com/astral-sh/uv/issues/2541 +# uv issues: https://github.com/astral-sh/uv/issues/3437 & https://github.com/astral-sh/uv/issues/2541 RUN --mount=type=cache,target=/root/.cache,sharing=locked \ uv venv && \ ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \ From 50332246ad280bf08f4f612b5039d34b19f6b8ec Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Wed, 8 May 2024 13:41:15 +0530 Subject: [PATCH 041/114] Fix CI --- notebooks/api/0.8/10-container-images.ipynb | 10 ++++++---- packages/syft/src/syft/orchestra.py | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/notebooks/api/0.8/10-container-images.ipynb b/notebooks/api/0.8/10-container-images.ipynb index 35fbfd926c0..c0ecd05dc83 100644 --- a/notebooks/api/0.8/10-container-images.ipynb +++ b/notebooks/api/0.8/10-container-images.ipynb @@ -43,13 +43,15 @@ "metadata": {}, "outputs": [], "source": [ - "# Uncomment this to run on single docker containers\n", - "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"remote\"\n", + "# Uncomment this to run the whole docker based custom workers\n", + "# os.environ[\"ORCHESTRA_DEPLOYMENT_TYPE\"] = \"container_stack\"\n", "# os.environ[\"DEV_MODE\"] = \"True\"\n", "\n", "\n", - "# Disable inmemory worker for remote stack\n", - "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\"remote\",)" + "# Disable inmemory worker for container stack\n", + "running_as_container = os.environ.get(\"ORCHESTRA_DEPLOYMENT_TYPE\") in (\n", + " \"container_stack\",\n", + ")" ] }, { diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index ad8a8b92f9d..e85d61e1220 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -20,7 +20,6 @@ from .node.enclave import Enclave from .node.gateway import Gateway from .node.server import serve_node -from .node.worker import Worker from .protocol.data_protocol import stage_protocol_changes from .service.response import SyftError from .util.util import find_available_port From 2a62d4877ed3307f286c97d5fd60c3fe681a79e9 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Wed, 8 May 2024 21:55:52 +0530 Subject: [PATCH 042/114] Use get_random_port instead of find_available_port --- packages/syft/src/syft/orchestra.py | 5 ++--- packages/syft/src/syft/util/util.py | 6 ++++++ packages/syft/tests/syft/zmq_queue_test.py | 4 +--- packages/syft/tests/utils/random_port.py | 8 -------- 4 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 packages/syft/tests/utils/random_port.py diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index e85d61e1220..c17e73a488a 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -22,7 +22,7 @@ from .node.server import serve_node from .protocol.data_protocol import stage_protocol_changes from .service.response import SyftError -from .util.util import find_available_port +from .util.util import get_random_port DEFAULT_PORT = 8080 DEFAULT_URL = "http://localhost" @@ -196,8 +196,7 @@ def deploy_to_python( if port: kwargs["in_memory_workers"] = True if port == "auto": - # dont use default port to prevent port clashes in CI - port = find_available_port(host="localhost", port=None, search=True) + port = get_random_port() kwargs["port"] = port sig = inspect.signature(serve_node) diff --git a/packages/syft/src/syft/util/util.py b/packages/syft/src/syft/util/util.py index 84e8e5f1c80..38af45717a8 100644 --- a/packages/syft/src/syft/util/util.py +++ b/packages/syft/src/syft/util/util.py @@ -344,6 +344,12 @@ def find_available_port( return port +def get_random_port() -> int: + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + soc.bind(("", 0)) + return soc.getsockname()[1] + + def get_loaded_syft() -> ModuleType: return sys.modules[__name__.split(".")[0]] diff --git a/packages/syft/tests/syft/zmq_queue_test.py b/packages/syft/tests/syft/zmq_queue_test.py index 9b22ac7d260..d57b9f3da3e 100644 --- a/packages/syft/tests/syft/zmq_queue_test.py +++ b/packages/syft/tests/syft/zmq_queue_test.py @@ -21,9 +21,7 @@ from syft.service.response import SyftError from syft.service.response import SyftSuccess from syft.util.util import get_queue_address - -# relative -from ..utils.random_port import get_random_port +from syft.util.util import get_random_port @pytest.fixture diff --git a/packages/syft/tests/utils/random_port.py b/packages/syft/tests/utils/random_port.py deleted file mode 100644 index c3370694afb..00000000000 --- a/packages/syft/tests/utils/random_port.py +++ /dev/null @@ -1,8 +0,0 @@ -# stdlib -import socket - - -def get_random_port(): - soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - soc.bind(("", 0)) - return soc.getsockname()[1] From b835f23feb6754d9259297ec994cf4b0a64be614 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Wed, 8 May 2024 22:56:10 +0530 Subject: [PATCH 043/114] Refactor get_random_port and remove potential security vulnerability --- packages/syft/src/syft/orchestra.py | 4 ++-- packages/syft/src/syft/util/util.py | 14 ++++++++++---- packages/syft/tests/syft/zmq_queue_test.py | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/syft/src/syft/orchestra.py b/packages/syft/src/syft/orchestra.py index c17e73a488a..ffa2fe077c9 100644 --- a/packages/syft/src/syft/orchestra.py +++ b/packages/syft/src/syft/orchestra.py @@ -22,7 +22,7 @@ from .node.server import serve_node from .protocol.data_protocol import stage_protocol_changes from .service.response import SyftError -from .util.util import get_random_port +from .util.util import get_random_available_port DEFAULT_PORT = 8080 DEFAULT_URL = "http://localhost" @@ -196,7 +196,7 @@ def deploy_to_python( if port: kwargs["in_memory_workers"] = True if port == "auto": - port = get_random_port() + port = get_random_available_port() kwargs["port"] = port sig = inspect.signature(serve_node) diff --git a/packages/syft/src/syft/util/util.py b/packages/syft/src/syft/util/util.py index 38af45717a8..b0affa2b1a0 100644 --- a/packages/syft/src/syft/util/util.py +++ b/packages/syft/src/syft/util/util.py @@ -344,10 +344,16 @@ def find_available_port( return port -def get_random_port() -> int: - soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - soc.bind(("", 0)) - return soc.getsockname()[1] +def get_random_available_port() -> int: + """Retrieve a random available port number from the host OS. + + Returns + ------- + int: Available port number. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as soc: + soc.bind(("localhost", 0)) + return soc.getsockname()[1] def get_loaded_syft() -> ModuleType: diff --git a/packages/syft/tests/syft/zmq_queue_test.py b/packages/syft/tests/syft/zmq_queue_test.py index d57b9f3da3e..8c5b8dedebe 100644 --- a/packages/syft/tests/syft/zmq_queue_test.py +++ b/packages/syft/tests/syft/zmq_queue_test.py @@ -21,7 +21,7 @@ from syft.service.response import SyftError from syft.service.response import SyftSuccess from syft.util.util import get_queue_address -from syft.util.util import get_random_port +from syft.util.util import get_random_available_port @pytest.fixture @@ -116,7 +116,7 @@ def handle_message(message: bytes, *args, **kwargs): @pytest.fixture def producer(): - pub_port = get_random_port() + pub_port = get_random_available_port() QueueName = token_hex(8) # Create a producer From e32bd9b5f07fdfd5553b1174a8f12aafbe6994cd Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 07:40:59 +0530 Subject: [PATCH 044/114] [review] remove unused variables --- packages/syft/src/syft/client/client.py | 10 ---------- .../integration/container_workload/pool_image_test.py | 1 - tox.ini | 2 -- 3 files changed, 13 deletions(-) diff --git a/packages/syft/src/syft/client/client.py b/packages/syft/src/syft/client/client.py index 9438294a6c0..4fbe2738bd8 100644 --- a/packages/syft/src/syft/client/client.py +++ b/packages/syft/src/syft/client/client.py @@ -8,7 +8,6 @@ from enum import Enum from getpass import getpass import json -import os from typing import Any from typing import TYPE_CHECKING from typing import cast @@ -768,15 +767,6 @@ def login( register: bool = False, **kwargs: Any, ) -> Self: - # TODO: Remove this Hack (Note to Rasswanth) - # If SYFT_LOGIN_{NODE_NAME}_PASSWORD is set, use that as the password - # for the login. This is useful for CI/CD environments to test password - # randomization that is implemented by helm charts - if self.name is not None and email == "info@openmined.org": - pass_env_var = f"SYFT_LOGIN_{self.name}_PASSWORD" - if pass_env_var in os.environ: - password = os.environ[pass_env_var] - if email is None: email = input("Email: ") if password is None: diff --git a/tests/integration/container_workload/pool_image_test.py b/tests/integration/container_workload/pool_image_test.py index 2695fbcb1c7..96613240660 100644 --- a/tests/integration/container_workload/pool_image_test.py +++ b/tests/integration/container_workload/pool_image_test.py @@ -134,7 +134,6 @@ def test_pool_launch(domain_1_port, external_registry_uid) -> None: assert isinstance(docker_build_result, SyftSuccess) # Push Image to External registry - push_result = None push_result = domain_client.api.services.worker_image.push( worker_image.id, username=external_registry_username, diff --git a/tox.ini b/tox.ini index 80a811bd139..48743f1d64a 100644 --- a/tox.ini +++ b/tox.ini @@ -700,8 +700,6 @@ commands = sleep 30 - # wait for front end - # wait for test gateway 1 bash packages/grid/scripts/wait_for.sh service mongo --context k3d-{env:GATEWAY_CLUSTER_NAME} --namespace syft bash packages/grid/scripts/wait_for.sh service backend --context k3d-{env:GATEWAY_CLUSTER_NAME} --namespace syft From c56e32bb156099c62280eb5e0013409e6d1d8d32 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:04:12 +0530 Subject: [PATCH 045/114] remove hagrid in tox testing --- tox.ini | 116 -------------------------------------------------------- 1 file changed, 116 deletions(-) diff --git a/tox.ini b/tox.ini index 90207455b55..7811433c6e6 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,6 @@ envlist = dev.k8s.cleanup dev.k8s.destroy dev.k8s.destroyall - hagrid.publish lint stack.test.integration syft.docs @@ -69,16 +68,6 @@ allowlist_externals = commands = bash -c 'uv pip list || pip list' -[testenv:hagrid] -deps = - -e{toxinidir}/packages/hagrid[dev] -changedir = {toxinidir}/packages/hagrid -description = Syft -allowlist_externals = - bash -commands = - bash -c 'uv pip list || pip list' - [testenv:syftcli] deps = -e{toxinidir}/packages/syftcli[dev] @@ -98,14 +87,6 @@ commands = python -c 'from shutil import rmtree; rmtree("build", True); rmtree("dist", True)' python -m build . -[testenv:hagrid.publish] -changedir = {toxinidir}/packages/hagrid -description = Build and Publish Hagrid Wheel -deps = - build -commands = - python -c 'from shutil import rmtree; rmtree("build", True); rmtree("dist", True)' - python -m build . [testenv:syftcli.publish] changedir = {toxinidir}/packages/syftcli @@ -179,8 +160,6 @@ commands = [testenv:frontend.test.unit] description = Frontend Unit Tests -deps = - {[testenv:hagrid]deps} allowlist_externals = docker bash @@ -203,99 +182,11 @@ commands = -[testenv:stack.test.integration] -description = Integration Tests for Core Stack -deps = - {[testenv:syft]deps} - {[testenv:hagrid]deps} - pytest -changedir = {toxinidir} -allowlist_externals = - docker - grep - sleep - bash - chcp -passenv=HOME, USER, AZURE_BLOB_STORAGE_KEY -setenv = - HAGRID_FLAGS = {env:HAGRID_FLAGS:--tag=local --dev} - EMULATION = {env:EMULATION:false} - HAGRID_ART = false - PYTHONIOENCODING = utf-8 - PYTEST_MODULES = {env:PYTEST_MODULES:frontend container_workload network local} -commands = - bash -c "whoami; id;" - - bash -c "echo Running with HAGRID_FLAGS=$HAGRID_FLAGS EMULATION=$EMULATION PYTEST_MODULES=$PYTEST_MODULES; date" - - ; install syft and hagrid - bash -c 'if [[ "$HAGRID_FLAGS" == *"latest"* ]]; then \ - echo "Installing latest syft and hagrid"; \ - uv pip install --force hagrid syft; \ - elif [[ "$HAGRID_FLAGS" == *"beta"* ]]; then \ - echo "Installing beta syft and hagrid"; \ - uv pip install --force hagrid; \ - uv pip install --force -U --pre syft; \ - else \ - echo "Using local syft and hagrid"; \ - fi' - - ; fix windows encoding - - chcp 65001 - - ; check docker versions - bash -c "docker --version" - bash -c "docker compose version" - - ; reset volumes and create nodes - bash -c "echo Starting Nodes; date" - bash -c 'docker rm -f $(docker ps -a -q --filter "label=orgs.openmined.syft") || true' - bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' - - python -c 'import syft as sy; sy.stage_protocol_changes()' - - ; Make sure that pacakge-cache is owned by the current user - ; instead of docker creating it as root - bash -c 'mkdir -p packages/grid/data/package-cache' - - bash -c 'HAGRID_ART=$HAGRID_ART hagrid launch test-gateway-1 gateway to docker:9081 $HAGRID_FLAGS --no-health-checks --verbose --no-warnings --build' - bash -c 'HAGRID_ART=$HAGRID_ART hagrid launch test-domain-1 domain to docker:9082 $HAGRID_FLAGS --no-health-checks --enable-signup --verbose --no-warnings --build' - ; bash -c 'HAGRID_ART=$HAGRID_ART hagrid launch test-domain-2 domain to docker:9083 --headless $HAGRID_FLAGS --enable-signup --no-health-checks --verbose --no-warnings --build' - - ; wait for nodes to start - docker ps - bash -c "echo Waiting for Nodes; date" - bash -c '(docker logs test-domain-1-frontend-1 -f &) | grep -q -E "Network:\s+https?://[a-zA-Z0-9.-]+:[0-9]+/" || true' - bash -c '(docker logs test-domain-1-backend-1 -f &) | grep -q "Application startup complete" || true' - ; bash -c '(docker logs test_domain_2-backend-1 -f &) | grep -q "Application startup complete" || true' - bash -c '(docker logs test-gateway-1-backend-1 -f &) | grep -q "Application startup complete" || true' - - bash -c '\ - PYTEST_MODULES=($PYTEST_MODULES); \ - for i in "${PYTEST_MODULES[@]}"; do \ - echo "Starting test for $i"; date; \ - pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ - return=$?; \ - echo "Finished $i"; \ - date; \ - if [[ $return -ne 0 ]]; then \ - exit $return; \ - fi; \ - done' - - ; shutdown - bash -c "echo Killing Nodes; date" - bash -c 'HAGRID_ART=false hagrid land all --force' - bash -c 'docker rm -f $(docker ps -a -q --filter "label=orgs.openmined.syft") || true' - bash -c 'docker volume rm -f $(docker volume ls -q --filter "label=orgs.openmined.syft") || true' - - [testenv:syft.docs] description = Build Docs for Syft changedir = {toxinidir}/docs deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} -r {toxinidir}/docs/requirements.txt allowlist_externals = make @@ -316,7 +207,6 @@ commands = description = Jupyter Notebook with Editable Syft deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} jupyter jupyterlab commands = @@ -343,7 +233,6 @@ description = Security Checks for Syft changedir = {toxinidir}/packages/syft deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} commands = bandit -r src # ansible 8.4.0 @@ -354,7 +243,6 @@ commands = description = Syft Unit Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} allowlist_externals = bash uv @@ -437,7 +325,6 @@ commands = description = Stack Notebook Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake changedir = {toxinidir}/notebooks allowlist_externals = @@ -473,7 +360,6 @@ commands = description = Stack VM Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake allowlist_externals = cd @@ -523,7 +409,6 @@ commands = description = Stack podman Tests for Rhel & Centos deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake allowlist_externals = cd @@ -556,7 +441,6 @@ commands = description = Generate Types for Frontend deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} allowlist_externals = cd bash From 146d0e2c8401a960981c696f7f80f735d2009403 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:11:29 +0530 Subject: [PATCH 046/114] [workflow] remove hagrid references in github workflows --- .github/workflows/cd-hagrid.yml | 108 ------------------ .github/workflows/cd-post-release-tests.yml | 99 ---------------- .github/workflows/cd-syft.yml | 4 +- .github/workflows/e2e-tests-notebook.yml | 2 +- .../manual-delete-buildjet-cache.yml | 22 ---- .github/workflows/nightlies.yml | 4 - .github/workflows/pr-tests-frontend.yml | 2 +- .github/workflows/pr-tests-hagrid.yml | 81 ------------- .github/workflows/test-github-arc.yml | 28 ----- 9 files changed, 3 insertions(+), 347 deletions(-) delete mode 100644 .github/workflows/cd-hagrid.yml delete mode 100644 .github/workflows/manual-delete-buildjet-cache.yml delete mode 100644 .github/workflows/pr-tests-hagrid.yml delete mode 100644 .github/workflows/test-github-arc.yml diff --git a/.github/workflows/cd-hagrid.yml b/.github/workflows/cd-hagrid.yml deleted file mode 100644 index a17f61ec519..00000000000 --- a/.github/workflows/cd-hagrid.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: CD - HAGrid - -on: - schedule: - - cron: "00 10 * * */3" # At 10:00 UTC on every three days - - workflow_dispatch: - inputs: - skip_tests: - description: "If true, skip tests" - required: false - default: "false" - -# Prevents concurrent runs of the same workflow -# while the previous run is still in progress -concurrency: - group: "CD - Hagrid" - cancel-in-progress: false - -jobs: - call-pr-tests-linting: - if: github.repository == 'OpenMined/PySyft' && (github.event.inputs.skip_tests == 'false' || github.event_name == 'schedule') # don't run on forks - uses: OpenMined/PySyft/.github/workflows/pr-tests-linting.yml@dev - - call-pr-tests-syft: - if: github.repository == 'OpenMined/PySyft' && (github.event.inputs.skip_tests == 'false' || github.event_name == 'schedule') # don't run on forks - uses: OpenMined/PySyft/.github/workflows/pr-tests-syft.yml@dev - - call-pr-tests-stack: - if: github.repository == 'OpenMined/PySyft' && (github.event.inputs.skip_tests == 'false' || github.event_name == 'schedule') # don't run on forks - uses: OpenMined/PySyft/.github/workflows/pr-tests-stack.yml@dev - secrets: inherit - - call-hagrid-tests: - if: github.repository == 'OpenMined/PySyft' && (github.event.inputs.skip_tests == 'false' || github.event_name == 'schedule') # don't run on forks - uses: OpenMined/PySyft/.github/workflows/pr-tests-hagrid.yml@dev - - deploy-hagrid: - needs: - [ - call-pr-tests-linting, - call-pr-tests-syft, - call-pr-tests-stack, - call-hagrid-tests, - ] - if: always() && (needs.call-pr-tests-linting.result == 'success' && needs.call-pr-tests-syft.result == 'success' && needs.call-pr-tests-stack.result == 'success' && needs.call-hagrid-tests.result == 'success' || github.event.inputs.skip_tests == 'true') - runs-on: ubuntu-latest - - outputs: - current_hash: ${{ steps.get_hash.outputs.current_hash }} - previous_hash: ${{ steps.get_hash.outputs.previous_hash }} - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.SYFT_BOT_COMMIT_TOKEN }} - - name: Install checksumdir - run: | - pip install --upgrade checksumdir - - name: Get the hashes - id: get-hashes - shell: bash - run: | - current_hash=$(checksumdir ./packages/hagrid) - echo "current_hash=$current_hash" >> $GITHUB_OUTPUT - previous_hash=$(cat ./scripts/hagrid_hash) - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install dependencies - if: ${{needs.hagrid-deploy.outputs.current_hash}} != ${{needs.hagrid-deploy.outputs.previous_hash}} - run: | - python -m pip install --upgrade pip - pip install --upgrade tox setuptools wheel twine bump2version PyYAML - - - name: Bump the Version - if: ${{needs.hagrid-deploy.outputs.current_hash}} != ${{needs.hagrid-deploy.outputs.previous_hash}} - run: | - python3 hagrid/version.py - python3 scripts/update_manifest.py - bump2version patch --allow-dirty --no-commit - tox -e lint || true - python3 hagrid/version.py - working-directory: ./packages/hagrid - - - name: Write the new hash - if: ${{needs.hagrid-deploy.outputs.current_hash}} != ${{needs.hagrid-deploy.outputs.previous_hash}} - run: echo $(checksumdir packages/hagrid) > ./scripts/hagrid_hash - - - name: Commit changes - if: ${{needs.hagrid-deploy.outputs.current_hash}} != ${{needs.hagrid-deploy.outputs.previous_hash}} - uses: EndBug/add-and-commit@v9 - with: - author_name: ${{ secrets.OM_BOT_NAME }} - author_email: ${{ secrets.OM_BOT_EMAIL }} - message: "[hagrid] bump version" - add: "['./packages/hagrid/.bumpversion.cfg','./packages/hagrid/setup.py','./packages/hagrid/hagrid/version.py', './scripts/hagrid_hash', './packages/hagrid/hagrid/manifest_template.yml']" - - - name: Build and publish - if: ${{needs.hagrid-deploy.outputs.current_hash}} != ${{needs.hagrid-deploy.outputs.previous_hash}} - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.HAGRID_BUMP_TOKEN }} - run: | - tox -e hagrid.publish - twine upload packages/hagrid/dist/* diff --git a/.github/workflows/cd-post-release-tests.yml b/.github/workflows/cd-post-release-tests.yml index 36bd38c6131..1dee67e1be6 100644 --- a/.github/workflows/cd-post-release-tests.yml +++ b/.github/workflows/cd-post-release-tests.yml @@ -30,105 +30,6 @@ on: default: "REAL_PYPI" jobs: - notebook-test-hagrid: - if: github.event.inputs.release_platform == 'REAL_PYPI' - strategy: - max-parallel: 99 - matrix: - os: [ubuntu-latest] - python-version: ["3.12"] - - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - # free 10GB of space - - name: Remove unnecessary files - if: matrix.os == 'ubuntu-latest' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - docker image prune --all --force - docker builder prune --all --force - docker system prune --all --force - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - run: | - python -m pip install --upgrade --user pip - - - name: Get pip cache dir - id: pip-cache - shell: bash - run: | - echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - - - name: pip cache - uses: actions/cache@v4 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip-py${{ matrix.python-version }}- - - - name: Install Hagrid, tox and uv - run: | - pip install -U hagrid - pip install --upgrade pip uv==0.1.35 tox tox-uv==1.5.1 - - - name: Hagrid Version - run: | - hagrid version - - - name: Remove existing containers - continue-on-error: true - shell: bash - run: | - docker rm $(docker ps -aq) --force || true - docker volume prune -f || true - docker buildx use default || true - - - name: Launch Domain - run: | - hagrid launch test-domain-1 to docker:8081 --tag=${{ inputs.syft_version }} --low-side - - - name: Run tests - env: - NODE_PORT: "8081" - SYFT_VERSION: ${{ inputs.syft_version }} - EXCLUDE_NOTEBOOKS: "not 11-container-images-k8s.ipynb" - run: | - tox -e e2e.test.notebook - - #Run log collector python script - - name: Run log collector - timeout-minutes: 5 - if: failure() - shell: bash - run: | - python ./scripts/container_log_collector.py - - # Get Job name and url - - name: Get job name and url - id: job_name - if: failure() - shell: bash - run: | - echo "job_name=$(echo ${{ github.job }})" >> $GITHUB_OUTPUT - echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Upload logs to GitHub - uses: actions/upload-artifact@master - if: failure() - with: - name: ${{ matrix.os }}-${{ steps.job_name.outputs.job_name }}-logs-${{ steps.job_name.outputs.date }} - path: ./logs/${{ steps.job_name.outputs.job_name}}/ - syft-install-check: strategy: max-parallel: 99 diff --git a/.github/workflows/cd-syft.yml b/.github/workflows/cd-syft.yml index b842cd0d84e..c153465dd6a 100644 --- a/.github/workflows/cd-syft.yml +++ b/.github/workflows/cd-syft.yml @@ -396,7 +396,6 @@ jobs: bump2version prenum --allow-dirty --no-commit ls **/VERSION | xargs -I {} python {} cat packages/grid/devspace.yaml | grep '0\.' - python packages/hagrid/scripts/update_manifest.py $(python packages/grid/VERSION) - name: Generate Release Metadata id: release_checks @@ -460,7 +459,7 @@ jobs: author_name: ${{ secrets.OM_BOT_NAME }} author_email: ${{ secrets.OM_BOT_EMAIL }} message: "[syft]bump version" - add: "['.bumpversion.cfg', 'VERSION', 'packages/grid/VERSION','packages/syft/PYPI.md', 'packages/grid/devspace.yaml', 'packages/syft/src/syft/VERSION', 'packages/syft/setup.cfg', 'packages/grid/frontend/package.json', 'packages/syft/src/syft/__init__.py', 'packages/hagrid/hagrid/manifest_template.yml', 'packages/grid/helm/syft/Chart.yaml','packages/grid/helm/repo', 'packages/hagrid/hagrid/deps.py', 'packages/grid/podman/podman-kube/podman-syft-kube.yaml' ,'packages/grid/podman/podman-kube/podman-syft-kube-config.yaml', 'packages/syftcli/manifest.yml', 'packages/syft/src/syft/protocol/protocol_version.json', 'packages/syft/src/syft/protocol/releases/', 'packages/grid/backend/grid/images/worker_cpu.dockerfile','packages/grid/helm/syft/values.yaml','packages/grid/helm/syft']" + add: "['.bumpversion.cfg', 'VERSION', 'packages/grid/VERSION','packages/syft/PYPI.md', 'packages/grid/devspace.yaml', 'packages/syft/src/syft/VERSION', 'packages/syft/setup.cfg', 'packages/grid/frontend/package.json', 'packages/syft/src/syft/__init__.py', 'packages/grid/helm/syft/Chart.yaml','packages/grid/helm/repo', 'packages/grid/podman/podman-kube/podman-syft-kube.yaml' ,'packages/grid/podman/podman-kube/podman-syft-kube-config.yaml', 'packages/syftcli/manifest.yml', 'packages/syft/src/syft/protocol/protocol_version.json', 'packages/syft/src/syft/protocol/releases/', 'packages/grid/backend/grid/images/worker_cpu.dockerfile','packages/grid/helm/syft/values.yaml','packages/grid/helm/syft']" - name: Changes to commit to Syft Repo during stable release if: needs.merge-docker-images.outputs.release_tag == 'latest' @@ -539,7 +538,6 @@ jobs: files: | ./packages/syftcli/manifest.yml ./build/syftcli-config/* - ./packages/hagrid/hagrid/manifest_template.yml tag_name: v${{ steps.release_checks.outputs.github_release_version }} # Checkout to gh-pages and update helm repo diff --git a/.github/workflows/e2e-tests-notebook.yml b/.github/workflows/e2e-tests-notebook.yml index a7fe68cee6b..10c3eb84e2d 100644 --- a/.github/workflows/e2e-tests-notebook.yml +++ b/.github/workflows/e2e-tests-notebook.yml @@ -40,7 +40,7 @@ on: type: string jobs: - notebook-test-hagrid: + notebook-test-e2e: strategy: max-parallel: 99 matrix: diff --git a/.github/workflows/manual-delete-buildjet-cache.yml b/.github/workflows/manual-delete-buildjet-cache.yml deleted file mode 100644 index 97370c02406..00000000000 --- a/.github/workflows/manual-delete-buildjet-cache.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Manually Delete BuildJet Cache -on: - workflow_dispatch: - inputs: - cache_key: - description: "BuildJet Cache Key to Delete" - required: true - type: string -jobs: - manually-delete-buildjet-cache: - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.10", "3.11", "3.12"] - - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: buildjet/cache-delete@v1 - with: - cache_key: ${{ inputs.cache_key }} diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 6db4d8df53c..491b4dd6aad 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -14,10 +14,6 @@ jobs: if: github.repository == 'OpenMined/PySyft' # don't run on forks uses: OpenMined/PySyft/.github/workflows/pr-tests-linting.yml@dev - call-pr-tests-hagrid: - if: github.repository == 'OpenMined/PySyft' # don't run on forks - uses: OpenMined/PySyft/.github/workflows/pr-tests-hagrid.yml@dev - call-pr-tests-syft: if: github.repository == 'OpenMined/PySyft' # don't run on forks uses: OpenMined/PySyft/.github/workflows/pr-tests-syft.yml@dev diff --git a/.github/workflows/pr-tests-frontend.yml b/.github/workflows/pr-tests-frontend.yml index 7d669b61da8..7f19740dadb 100644 --- a/.github/workflows/pr-tests-frontend.yml +++ b/.github/workflows/pr-tests-frontend.yml @@ -61,7 +61,7 @@ jobs: if: steps.changes.outputs.frontend == 'true' with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-uv-py${{ matrix.python-version }}-${{ hashFiles('packages/hagrid/setup.cfg') }} + key: ${{ runner.os }}-uv-py${{ matrix.python-version }}-frontend restore-keys: | ${{ runner.os }}-uv-py${{ matrix.python-version }}- diff --git a/.github/workflows/pr-tests-hagrid.yml b/.github/workflows/pr-tests-hagrid.yml deleted file mode 100644 index a8e0e30e93f..00000000000 --- a/.github/workflows/pr-tests-hagrid.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: PR Tests - HAGrid - -on: - workflow_call: - - pull_request: - branches: - - dev - - main - - "0.8" - -concurrency: - group: hagrid-${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || github.workflow_ref }} - cancel-in-progress: true - -defaults: - run: - working-directory: ./packages/hagrid - -jobs: - pr-tests-syft-hagrid-comptability: - strategy: - max-parallel: 99 - matrix: - os: [ubuntu-latest] - python-version: ["3.11"] - syft-version: ["0.8.2", "0.8.2b6", "0.8.3"] - - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - - name: Check for file changes - uses: dorny/paths-filter@v3 - id: changes - with: - base: ${{ github.ref }} - token: ${{ github.token }} - filters: .github/file-filters.yml - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - if: steps.changes.outputs.hagrid == 'true' - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - if: steps.changes.outputs.hagrid == 'true' - run: | - python -m pip install --upgrade --user pip - - - name: Get pip cache dir - id: pip-cache - if: steps.changes.outputs.hagrid == 'true' - shell: bash - run: | - echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - - - name: pip cache - uses: actions/cache@v4 - if: steps.changes.outputs.hagrid == 'true' - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('packages/syft/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('packages/syft/setup.cfg') }} - - # https://github.com/google/jax/issues/17693 - # pinning ml-dtypes due to jax version==0.4.10 - - name: Install Syft ${{ matrix.syft-version }} - if: steps.changes.outputs.hagrid == 'true' - run: | - pip install ml-dtypes==0.2.0 - pip install syft==${{ matrix.syft-version }} - pip install . - - - name: Run Orchestra Command - if: steps.changes.outputs.hagrid == 'true' - run: | - python -c "import syft as sy; domain1 = sy.orchestra.launch(name='test-domain-1', dev_mode=True, reset=True)" - python -c "import syft as sy; domain2 = sy.orchestra.launch(name='test-domain-2',dev_mode=False, reset=True)" diff --git a/.github/workflows/test-github-arc.yml b/.github/workflows/test-github-arc.yml deleted file mode 100644 index 4f3dfacfa29..00000000000 --- a/.github/workflows/test-github-arc.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Actions Runner Controller Demo -on: - workflow_dispatch: - -jobs: - Test-Github-ARC-x64: - # You need to use the INSTALLATION_NAME from the previous step - runs-on: sh-arc-linux-x64 - steps: - - name: "Test Github ARC" - run: | - echo "πŸŽ‰ This job uses runner scale set runners!" - - - name: "Check Architecture" - run: | - uname -a - - Test-Github-ARC-arm64: - # You need to use the INSTALLATION_NAME from the previous step - runs-on: sh-arc-linux-arm64 - steps: - - name: "Test Github ARC" - run: | - echo "πŸŽ‰ This job uses runner scale set runners!" - - - name: "Check Architecture" - run: | - uname -a From 12e32bd9b4e40084f813378e1b3caa250a43d84c Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:13:40 +0530 Subject: [PATCH 047/114] [workflow] remove additional references of hagrid --- .github/file-filters.yml | 11 ----------- .github/workflows/cd-post-release-tests.yml | 3 +-- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/file-filters.yml b/.github/file-filters.yml index 1d0a44134cc..be000a84640 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -27,17 +27,6 @@ backend: - packages/grid/backend/**/*.sh - packages/grid/backend/**/*.mako -hagrid: - - .github/workflows/pr-tests-hagrid.yml - - packages/hagrid/**/*.py - - packages/hagrid/**/*.cfg - - packages/hagrid/**/*.yml - - packages/hagrid/**/*.dockerfile - - packages/hagrid/**/*.toml - - packages/hagrid/**/*.txt - - packages/hagrid/**/*.ini - - packages/hagrid/**/*.sh - syft: - .github/workflows/pr-tests-syft.yml - packages/syft/**/*.py diff --git a/.github/workflows/cd-post-release-tests.yml b/.github/workflows/cd-post-release-tests.yml index 1dee67e1be6..370469ea0bb 100644 --- a/.github/workflows/cd-post-release-tests.yml +++ b/.github/workflows/cd-post-release-tests.yml @@ -191,9 +191,8 @@ jobs: pip install syft[data_science,dev]==${{ inputs.syft_version }} fi - - name: Install Hagrid, tox and uv + - name: Install tox and uv run: | - pip install -U hagrid pip install --upgrade pip uv==0.1.35 tox tox-uv==1.5.1 tox-current-env - name: Run unit tests From 8275212e9b02e17d1d16ff260415f770bd128905 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:15:44 +0530 Subject: [PATCH 048/114] [hagrid] remove packages/hagrid folder --- packages/hagrid/.bumpversion.cfg | 12 - packages/hagrid/.dockerignore | 11 - packages/hagrid/.gitattributes | 2 - packages/hagrid/.gitignore | 128 - packages/hagrid/README.md | 219 - packages/hagrid/build_docker.ps1 | 2 - packages/hagrid/build_docker.sh | 3 - packages/hagrid/build_wheel.sh | 4 - packages/hagrid/cli2.png | Bin 412982 -> 0 bytes packages/hagrid/hagrid.dockerfile | 27 - packages/hagrid/hagrid/__init__.py | 43 - packages/hagrid/hagrid/art.py | 125 - packages/hagrid/hagrid/auth.py | 25 - packages/hagrid/hagrid/azure.py | 67 - packages/hagrid/hagrid/cache.py | 69 - packages/hagrid/hagrid/cli.py | 4404 ------------------ packages/hagrid/hagrid/deps.py | 911 ---- packages/hagrid/hagrid/dummynum.py | 28 - packages/hagrid/hagrid/exceptions.py | 2 - packages/hagrid/hagrid/file.py | 8 - packages/hagrid/hagrid/git_check.py | 15 - packages/hagrid/hagrid/grammar.py | 365 -- packages/hagrid/hagrid/img/hagrid.png | Bin 422216 -> 0 bytes packages/hagrid/hagrid/img/hagrid2.png | Bin 189910 -> 0 bytes packages/hagrid/hagrid/land.py | 60 - packages/hagrid/hagrid/launch.py | 113 - packages/hagrid/hagrid/lib.py | 472 -- packages/hagrid/hagrid/manifest_template.yml | 31 - packages/hagrid/hagrid/mode.py | 47 - packages/hagrid/hagrid/names.py | 185 - packages/hagrid/hagrid/nb_output.py | 15 - packages/hagrid/hagrid/parse_template.py | 327 -- packages/hagrid/hagrid/quickstart_ui.py | 356 -- packages/hagrid/hagrid/rand_sec.py | 84 - packages/hagrid/hagrid/stable_version.py | 1 - packages/hagrid/hagrid/style.py | 44 - packages/hagrid/hagrid/util.py | 102 - packages/hagrid/hagrid/version.py | 6 - packages/hagrid/hagrid/win_bootstrap.py | 267 -- packages/hagrid/hagrid/wizard_ui.py | 62 - packages/hagrid/scripts/install.sh | 232 - packages/hagrid/scripts/update_manifest.py | 47 - packages/hagrid/setup.py | 47 - packages/hagrid/tests/__init__.py | 0 packages/hagrid/tests/hagrid/__init__.py | 0 packages/hagrid/tests/hagrid/cli_test.py | 199 - 46 files changed, 9167 deletions(-) delete mode 100644 packages/hagrid/.bumpversion.cfg delete mode 100644 packages/hagrid/.dockerignore delete mode 100644 packages/hagrid/.gitattributes delete mode 100644 packages/hagrid/.gitignore delete mode 100644 packages/hagrid/README.md delete mode 100644 packages/hagrid/build_docker.ps1 delete mode 100755 packages/hagrid/build_docker.sh delete mode 100755 packages/hagrid/build_wheel.sh delete mode 100644 packages/hagrid/cli2.png delete mode 100644 packages/hagrid/hagrid.dockerfile delete mode 100644 packages/hagrid/hagrid/__init__.py delete mode 100644 packages/hagrid/hagrid/art.py delete mode 100644 packages/hagrid/hagrid/auth.py delete mode 100644 packages/hagrid/hagrid/azure.py delete mode 100644 packages/hagrid/hagrid/cache.py delete mode 100644 packages/hagrid/hagrid/cli.py delete mode 100644 packages/hagrid/hagrid/deps.py delete mode 100644 packages/hagrid/hagrid/dummynum.py delete mode 100644 packages/hagrid/hagrid/exceptions.py delete mode 100644 packages/hagrid/hagrid/file.py delete mode 100644 packages/hagrid/hagrid/git_check.py delete mode 100644 packages/hagrid/hagrid/grammar.py delete mode 100644 packages/hagrid/hagrid/img/hagrid.png delete mode 100644 packages/hagrid/hagrid/img/hagrid2.png delete mode 100644 packages/hagrid/hagrid/land.py delete mode 100644 packages/hagrid/hagrid/launch.py delete mode 100644 packages/hagrid/hagrid/lib.py delete mode 100644 packages/hagrid/hagrid/manifest_template.yml delete mode 100644 packages/hagrid/hagrid/mode.py delete mode 100644 packages/hagrid/hagrid/names.py delete mode 100644 packages/hagrid/hagrid/nb_output.py delete mode 100644 packages/hagrid/hagrid/parse_template.py delete mode 100644 packages/hagrid/hagrid/quickstart_ui.py delete mode 100644 packages/hagrid/hagrid/rand_sec.py delete mode 100644 packages/hagrid/hagrid/stable_version.py delete mode 100644 packages/hagrid/hagrid/style.py delete mode 100644 packages/hagrid/hagrid/util.py delete mode 100644 packages/hagrid/hagrid/version.py delete mode 100644 packages/hagrid/hagrid/win_bootstrap.py delete mode 100644 packages/hagrid/hagrid/wizard_ui.py delete mode 100755 packages/hagrid/scripts/install.sh delete mode 100644 packages/hagrid/scripts/update_manifest.py delete mode 100644 packages/hagrid/setup.py delete mode 100644 packages/hagrid/tests/__init__.py delete mode 100644 packages/hagrid/tests/hagrid/__init__.py delete mode 100644 packages/hagrid/tests/hagrid/cli_test.py diff --git a/packages/hagrid/.bumpversion.cfg b/packages/hagrid/.bumpversion.cfg deleted file mode 100644 index fa5a840eab9..00000000000 --- a/packages/hagrid/.bumpversion.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[bumpversion] -current_version = 0.3.119 -tag = False -tag_name = {new_version} -commit = True -commit_message = Bump version: {current_version} β†’ {new_version} - -[bumpversion:file:hagrid/version.py] - -[bumpversion:file:setup.py] - -[bumpversion:file:hagrid/manifest_template.yml] diff --git a/packages/hagrid/.dockerignore b/packages/hagrid/.dockerignore deleted file mode 100644 index 1effc400690..00000000000 --- a/packages/hagrid/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -.git -.vscode -.mypy_cache -.benchmarks -build -dist -hagrid.egg-info -Pipfile -Dockerfile -README.md -.venv diff --git a/packages/hagrid/.gitattributes b/packages/hagrid/.gitattributes deleted file mode 100644 index dfe0770424b..00000000000 --- a/packages/hagrid/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/packages/hagrid/.gitignore b/packages/hagrid/.gitignore deleted file mode 100644 index 7ebe0d7df82..00000000000 --- a/packages/hagrid/.gitignore +++ /dev/null @@ -1,128 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -Pipfile -.envfile diff --git a/packages/hagrid/README.md b/packages/hagrid/README.md deleted file mode 100644 index a2d398960c1..00000000000 --- a/packages/hagrid/README.md +++ /dev/null @@ -1,219 +0,0 @@ -# hagrid - -Use this cli to deploy PyGrid Domain and Network nodes on your local machine. - -A Hagrid is a HAppy GRID! - -## Installation Linux and MacOS - -Python - -``` -$ pip install -U hagrid -``` - -Docker - -``` -$ docker run -it -v ~/:/root openmined/hagrid:latest hagrid -``` - -Then simply run hagrid as you would normally: - -``` -$ docker run -it -v ~/:/root openmined/hagrid:latest hagrid launch slytherin domain to azure -``` - -## Installation Windows - -Requirements: - -- docker - -### Docker - -You can manually install Docker Desktop: https://www.docker.com/products/docker-desktop - -or alternatively use the windows package manager `chocolatey`. - -### Chocolatey - -To install `chocolatey` you need to open "Windows PowerShell" in Administrator mode. -You can type `powershell` into the search bar and right-click and select "Run as administrator". If when you start Powershell in Administrator mode it asks you whether you want to allow powershell to make changes to your computer, select "Yes". -Then copy and paste this command and run it. - -Run this inside PowerShell (Admin Mode): - -```powershell -Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) -``` - -You should now be able to type: `choco` in PowerShell terminal to use the tool. - -### Docker with Chocolatey - -To install Docker Desktop with chocolatey run this inside PowerShell (Admin Mode): - -```powershell -choco install docker-desktop -y -``` - -You will likely need to restart after installing Docker Desktop for the first time. Also occasionally Powershell looks like its taking for ever but if you hit then it'll show that the command is actually done. - -### Restart the computer and start Docker Desktop - -After you've rebooted your Windows machine, launch the application "Docker Desktop" and continue. - -### Docker Backend - -Docker on Windows has two possible backends, one which uses a virtual machine and the other which uses Windows Subsystem for Linux 2. - -Try running Docker Desktop and seeing that it starts. -If you get an error saying "Docker Engine failed to start..." you may not have Virtualization enabled. -Either have it enabled in your BIOS if your CPU supports it, or you may need to use the Windows Subsystem for Linux 2 backend. - -To install WSL2 with chocolately run this inside PowerShell (Admin Mode): - -```powershell -choco install wsl2 -y -``` - -If you needed to install wsl2, restart docker by clicking the little whale in the bottom right corner, clicking "Stop" and then starting Docker Desktop application again. - -### Enable Docker Compose v2 - -Inside Docker Desktop click on the settings wheel in the top right. -Click on the menu item "Experimental Features" on the left. -Check the box that says: "Use Docker Desktop V2". - -## SSH Keys - -HAGrid allows you to select an SSH key, to setup a remote node. When using Docker on Windows we recommend you mount your Users %USERPROFILE% directory into the container so that any keys you already have can be accessed. If you have a key inside C:\Users\John Smith\.ssh\mykey.pem then when asked for the path to your key you would enter: `~/mykey.pem`. - -If HAGrid complains that you have no key, it can generate one for you, or you can always generate one yourself manually using the ssh-keygen.exe tool. - -To generate a key using ssh-keygen run in a Powershell: - -``` -ssh-keygen -``` - -Unless you know what the options are simply pressing enter and going with the defaults is perfectly fine. This will create a file called `~/.ssh/id_rsa` which is also the default that HAGrid asks you if you want to use. - -## Run HAGrid Docker Container - -```powershell -docker run -it -v "$($env:USERPROFILE):/root" openmined/hagrid:latest hagrid -``` - -Then simply run hagrid as you would normally: - -```powershell -docker run -it -v "$($env:USERPROFILE):/root" openmined/hagrid:latest hagrid launch slytherin to azure -``` - -## Development - -#### Step 1 Dev Setup - -If you want hagrid to launch nodes based on your live-updating codebase, then install it using one of the live-updating install commands. This will mean that the codebase will hot-reload based on the current project. - -```bash -pip install -e . -``` - -## Launch a Node - -![alt text](cli2.png) - -## A Few Example Commands - -Start a node with: - -```bash -hagrid launch slytherin -``` - -... and then stop it with: - -```bash -hagrid land slytherin -``` - -You can specify ports if you want to: - -```bash -hagrid launch hufflepuff_house to docker:8081+ -``` - -... but if you don't it'll find an open one for you - -```bash -// finds hufflepuff already has 8081... tries 8082 -hagrid launch ravenclaw -``` - -You can also specify the node type (domain by default) - -```bash -hagrid launch gryffendor network to docker -``` - -## Credits - -## Testing HAGrid Remotely - -Sometimes you need to install HAGrid directly from source while developing and testing on a remote machine. You can use the pip git syntax like so: - -``` -$ pip install "git+https://github.com/OpenMined/PySyft@demo_strike_team_branch_4#subdirectory=packages/hagrid" -``` - -## Deploying HAGrid to a running Linux Machine (Ubuntu 20.x) - -Log into your linux machine and run the following: - -``` -pip install -U hagrid -``` - -Often on a remote linux box hagrid will not by default show up in the path. Re-login via SSH terminal and hagrid should appear. - -``` -hagrid launch domain to localhost -``` - -Then folllow the instructions in the prompt. Note that occasionally you'll see a harmless (but red and scary looking) error at the very end of the deploy which looks something like: - -``` -"Container slytherin_flower_1 Starting", "Container slytherin_db_1 Starting", "Container slytherin_proxy_1 Starting", "Container slytherin_queue_1 Starting", "Container slytherin_queue_1 Started", "Container slytherin_db_1 Started", "Container slytherin_backend_1 Starting", "Container slytherin_celeryworker_1 Starting", "Container slytherin_pgadmin_1 Starting", "Container slytherin_proxy_1 Started", "Container slytherin_flower_1 Started", "Container slytherin_backend_1 Started", "Container slytherin_celeryworker_1 Started", "Container slytherin_pgadmin_1 Started", "Container slytherin_backend_stream_1 Starting", "Container slytherin_frontend_1 Starting", "Container slytherin_frontend_1 Started", "Container slytherin_backend_stream_1 Started", "Error response from daemon: cannot start a stopped process: unknown"], "stdout": "", "stdout_lines": []} -: cannot start a stopped process: unknown"], "stdout": "", "stdout_lines": []} - -PLAY RECAP *************************************************************************************************************************** -104.42.1.158 : ok=26 changed=21 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0 -``` - -If you see this don't worry about it. - -### Post install checks. - -Log into the openmined user. - -``` -sudo su - om -``` - -Check that the autoupdater is running correctly and pointed to the branch you specified. - -``` -sudo crontab -l -``` - -Should return something like - -``` -#Ansible: Update PySyft Repo -* * * * * /home/om/PySyft/packages/grid/scripts/cron.sh /home/om/PySyft The-PET-Lab-at-the-UN-PPTTT/PySyft ungp_pet_lab om om domain slytherin -``` - -**Super Cool Code Images** by [Carbon](https://carbon.now.sh/) diff --git a/packages/hagrid/build_docker.ps1 b/packages/hagrid/build_docker.ps1 deleted file mode 100644 index 29951ee1e4b..00000000000 --- a/packages/hagrid/build_docker.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -$env:HAGRID_VERSION = $(python hagrid/version.py) -docker buildx build -f hagrid.dockerfile -t openmined/hagrid:"$env:HAGRID_VERSION" -t openmined/hagrid:latest . \ No newline at end of file diff --git a/packages/hagrid/build_docker.sh b/packages/hagrid/build_docker.sh deleted file mode 100755 index 8c0bf150398..00000000000 --- a/packages/hagrid/build_docker.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -HAGRID_VERSION=$(python3 hagrid/version.py) -docker buildx build -f hagrid.dockerfile -t openmined/hagrid:"${HAGRID_VERSION}" -t openmined/hagrid:latest . diff --git a/packages/hagrid/build_wheel.sh b/packages/hagrid/build_wheel.sh deleted file mode 100755 index a5343a06a69..00000000000 --- a/packages/hagrid/build_wheel.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -rm -rf build dist -python setup.py bdist_wheel -rm -rf build diff --git a/packages/hagrid/cli2.png b/packages/hagrid/cli2.png deleted file mode 100644 index 1a31e0279215297d059844defc56386ee7d694e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 412982 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

1EHmY>#LrtB?ZTW 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 z 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%0Aaj^X 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 z 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 diff --git a/packages/hagrid/hagrid.dockerfile b/packages/hagrid/hagrid.dockerfile deleted file mode 100644 index 878aff613df..00000000000 --- a/packages/hagrid/hagrid.dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM python:3.12-slim as build - -WORKDIR /hagrid -COPY ./ /hagrid - -RUN pip install --upgrade pip setuptools wheel twine -RUN python setup.py bdist_wheel -RUN twine check `find -L ./dist -name "*.whl"` - -FROM python:3.12-slim as backend - -# set UTC timezone -ENV TZ=Etc/UTC -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN DEBIAN_FRONTEND=noninteractive \ - apt-get update && \ - apt-get install -yqq \ - git && \ - rm -rf /var/lib/apt/lists/* - -COPY --from=build /hagrid/dist /hagrid -RUN pip install `find -L /hagrid -name "*.whl"` - -# warm the cache -RUN hagrid -CMD hagrid diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py deleted file mode 100644 index eabd22f9f19..00000000000 --- a/packages/hagrid/hagrid/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -from .git_check import verify_git_installation # noqa - -# stdlib -import sys -from typing import Any - -# relative -from .cli import check_status as check # noqa: F401 -from .quickstart_ui import QuickstartUI -from .version import __version__ # noqa: F401 -from .wizard_ui import WizardUI - -from .orchestra import Orchestra # noqa - - -def module_property(func: Any) -> None: - """Decorator to turn module functions into properties. - Function names must be prefixed with an underscore.""" - module = sys.modules[func.__module__] - - def base_getattr(name: str) -> None: - raise AttributeError(f"module '{module.__name__}' has no attribute '{name}'") - - old_getattr = getattr(module, "__getattr__", base_getattr) - - def new_getattr(name: str) -> Any: - if f"_{name}" == func.__name__: - return func() - else: - return old_getattr(name) - - module.__getattr__ = new_getattr # type: ignore - return func - - -@module_property -def _quickstart() -> QuickstartUI: - return QuickstartUI() - - -@module_property -def _wizard() -> WizardUI: - return WizardUI() diff --git a/packages/hagrid/hagrid/art.py b/packages/hagrid/hagrid/art.py deleted file mode 100644 index a272ab9d52f..00000000000 --- a/packages/hagrid/hagrid/art.py +++ /dev/null @@ -1,125 +0,0 @@ -# stdlib -import locale -import secrets - -# third party -import ascii_magic -import rich -from rich.emoji import Emoji - - -def motorcycle() -> None: - print( - """ - ` - `.+yys/.` - ``/NMMMNNs` - `./shNMMMMMMNs`` `..` - `-smNMMNNMMMMMMN/.``......` - `.yNMMMMNmmmmNNMMm/.`....` - `:sdNMMMMMMNNNNddddds-`.`` `--. ` - `.+dNNNNMMMMMMMMMNNNNmddohmh//hddy/.```..` - `-hNMMMMMMMMMMMMNNdmNNMNNdNNd:sdyoo+/++:..` - ../mMMMMMMMMMMMMMMNNmmmmNMNmNNmdmd/hNNNd+:` - `:mMNNMMMMMMMMMMMMNMNNmmmNNNNNdNNd/NMMMMm:: - `:mMNNNMMMMMMMMMMMMMMMNNNNdNMNNmmNd:smMMmh// - ``/mMMMMMMMMMMMMMMMMMMMMMMNmdmNNMMNNNy/osoo/-` - `-sNMMMMMMMMMMMMMMMMMMMMMMMMNNmmMMMMNh-....` - `/dNMMMMMMMMMMMMMMMMMMMMMMMMMMMNNMMMNy.` - ``.omNNMMMMMMMMMMMMNMMMMMMMNmmmmNNMMMMN+` - `:hmNNMMMMMMMMMMMNo/ohNNNNho+os+-+hNys/` - -mNNNNNNMMMMMMMMm+``-yNdd+/mMMMms.-:` - .+dmNNNNMMMMMMNd:``:dNN+y`oMMMMMm-.` - `+dmmmNNNmmmmy+. `-+m/s/+MMMMm/-- - `+mmmhNy/-...``` ``-.-sosyys+/-` - ``.smmmsoo`` .oh+-:/:. - `.:odmdh/```` `.+d+`````` - ```/sydNdhy+.` ``-sds. - `:hdmhs::-```` `oNs.` -```.sdmh/`` `-ym+` - ``ssy+` `-yms.` - `` `:hNy-`` - ` `-yMN/``` - `-yNhy- - `/yNd/` - `:dNMs`` - `.+mNy/.` - `.+hNMMs`` - `:dMMMMh.`""" # noqa: W605 - ) - - -def hold_on_tight() -> None: - pass - - -def hagrid1() -> None: - # relative - from .lib import asset_path - - try: - ascii_magic.to_terminal( - ascii_magic.from_image_file( - img_path=str(asset_path()) + "/img/hagrid.png", columns=83 - ) - ) - except Exception: # nosec - pass - - -def hagrid2() -> None: - # relative - from .lib import asset_path - - try: - ascii_magic.to_terminal( - ascii_magic.from_image_file( - img_path=str(asset_path()) + "/img/hagrid2.png", columns=83 - ) - ) - except Exception: # nosec - pass - - -def quickstart_art() -> None: - text = """ -888 888 d8888 .d8888b. d8b 888 -888 888 d88888 d88P Y88b Y8P 888 -888 888 d88P888 888 888 888 -8888888888 d88P 888 888 888d888 888 .d88888 -888 888 d88P 888 888 88888 888P" 888 d88" 888 -888 888 d88P 888 888 888 888 888 888 888 -888 888 d8888888888 Y88b d88P 888 888 Y88b 888 -888 888 d88P 888 "Y8888P88 888 888 "Y88888 - - - .d88888b. d8b 888 888 888 -d88P" "Y88b Y8P 888 888 888 -888 888 888 888 888 -888 888 888 888 888 .d8888b 888 888 .d8888b 888888 8888b. 888d888 888888 -888 888 888 888 888 d88P" 888 .88P 88K 888 "88b 888P" 888 -888 Y8b 888 888 888 888 888 888888K "Y8888b. 888 .d888888 888 888 -Y88b.Y8b88P Y88b 888 888 Y88b. 888 "88b X88 Y88b. 888 888 888 Y88b. - "Y888888" "Y88888 888 "Y8888P 888 888 88888P' "Y888 "Y888888 888 "Y888 - Y8b -""" - console = rich.get_console() - console.print( - text, - style="bold", - justify="left", - new_line_start=True, - ) - - -def hagrid() -> None: - """Print a random hagrid image with the caption "hold on tight harry".""" - options = [motorcycle, hagrid1, hagrid2] - i = secrets.randbelow(3) - options[i]() - hold_on_tight() - - -class RichEmoji(Emoji): - def to_str(self) -> str: - return self._char.encode("utf-8").decode(locale.getpreferredencoding()) diff --git a/packages/hagrid/hagrid/auth.py b/packages/hagrid/hagrid/auth.py deleted file mode 100644 index b3cca8a35e5..00000000000 --- a/packages/hagrid/hagrid/auth.py +++ /dev/null @@ -1,25 +0,0 @@ -# stdlib - - -class AuthCredentials: - def __init__( - self, - username: str, - key_path: str | None = None, - password: str | None = None, - ) -> None: - self.username = username - self.key_path = key_path - self.password = password - - @property - def uses_key(self) -> bool: - return bool(self.username and self.key_path) - - @property - def uses_password(self) -> bool: - return bool(self.username and self.password) - - @property - def valid(self) -> bool: - return bool(self.uses_key or self.uses_password) diff --git a/packages/hagrid/hagrid/azure.py b/packages/hagrid/hagrid/azure.py deleted file mode 100644 index b84e1f32bd7..00000000000 --- a/packages/hagrid/hagrid/azure.py +++ /dev/null @@ -1,67 +0,0 @@ -# stdlib -import json -import os -import subprocess # nosec - -# third party -from azure.identity import ClientSecretCredential -from azure.mgmt.resource import ResourceManagementClient - -# relative -from .file import user_hagrid_profile - -AZURE_SERVICE_PRINCIPAL_PATH = f"{user_hagrid_profile}/azure_sp.json" - - -class AzureException(Exception): - pass - - -def check_azure_authed() -> bool: - try: - azure_service_principal() - return True - except AzureException as e: - print(e) - - return False - - -def login_azure() -> bool: - cmd = "az login" - try: - subprocess.check_call(cmd, shell=True, stdout=subprocess.DEVNULL) # nosec - return True - except Exception: # nosec - pass - return False - - -def azure_service_principal() -> dict[str, str] | None: - sp_json = {} - if not os.path.exists(AZURE_SERVICE_PRINCIPAL_PATH): - raise AzureException("No service principal so we need to create one first") - with open(AZURE_SERVICE_PRINCIPAL_PATH) as f: - sp_json = json.loads(f.read()) - - required_keys = ["appId", "displayName", "name", "password", "tenant"] - for key in required_keys: - if key not in sp_json: - raise AzureException(f"{key} missing from {AZURE_SERVICE_PRINCIPAL_PATH}") - return sp_json - - -def azure_credentials( - tenant_id: str, client_id: str, client_secret: str -) -> ClientSecretCredential: - return ClientSecretCredential( - tenant_id=tenant_id, - client_id=client_id, - client_secret=client_secret, - ) - - -def resource_management_client( - credentials: ClientSecretCredential, subscription_id: str -) -> ResourceManagementClient: - return ResourceManagementClient(credentials, subscription_id) diff --git a/packages/hagrid/hagrid/cache.py b/packages/hagrid/hagrid/cache.py deleted file mode 100644 index 5a94d6180aa..00000000000 --- a/packages/hagrid/hagrid/cache.py +++ /dev/null @@ -1,69 +0,0 @@ -# stdlib -import json -import os -from typing import Any - -STABLE_BRANCH = "0.8.6" -DEFAULT_BRANCH = "0.8.6" -DEFAULT_REPO = "OpenMined/PySyft" - -arg_defaults = { - "repo": DEFAULT_REPO, - "branch": STABLE_BRANCH, - "username": "root", - "auth_type": "key", - "key_path": "~/.ssh/id_rsa", - "azure_repo": DEFAULT_REPO, - "azure_branch": STABLE_BRANCH, - "azure_username": "azureuser", - "azure_key_path": "~/.ssh/id_rsa", - "azure_resource_group": "openmined", - "azure_location": "westus", - "azure_size": "Standard_D4s_v3", - "gcp_zone": "us-central1-c", - "gcp_machine_type": "e2-standard-4", - "gcp_project_id": "", - "gcp_username": "", - "gcp_key_path": "~/.ssh/google_compute_engine", - "gcp_repo": DEFAULT_REPO, - "gcp_branch": STABLE_BRANCH, - "install_wizard_complete": False, - "aws_region": "us-east-1", - "aws_security_group_name": "openmined_sg", - "aws_security_group_cidr": "0.0.0.0/0", - "aws_image_id": "ami-05de688637f3e33ee", # Ubuntu Server 22.04 LTS (HVM), SSD Volume Type - "aws_ec2_instance_type": "t2.xlarge", - "aws_ec2_instance_username": "ubuntu", # For Ubuntu AMI, the default user name is ubuntu - "aws_repo": DEFAULT_REPO, - "aws_branch": STABLE_BRANCH, -} - - -class ArgCache: - @staticmethod - def cache_file_path() -> str: - dir_path = os.path.expanduser("~/.hagrid") - os.makedirs(dir_path, exist_ok=True) - return f"{dir_path}/cache.json" - - def __init__(self) -> None: - try: - with open(ArgCache.cache_file_path()) as f: - self.__cache = json.loads(f.read()) - except Exception: # nosec - self.__cache = {} - - def __setitem__(self, key: str, value: Any) -> None: - self.__cache[key] = value - with open(ArgCache.cache_file_path(), "w") as f: - f.write(json.dumps(self.__cache)) - - def __getitem__(self, key: str) -> Any: - if key in self.__cache: - return self.__cache[key] - elif key in arg_defaults: - return arg_defaults[key] - raise KeyError(f"Can't find key {key} in ArgCache") - - -arg_cache = ArgCache() diff --git a/packages/hagrid/hagrid/cli.py b/packages/hagrid/hagrid/cli.py deleted file mode 100644 index 0e8efdc06a4..00000000000 --- a/packages/hagrid/hagrid/cli.py +++ /dev/null @@ -1,4404 +0,0 @@ -# stdlib -from collections import namedtuple -from collections.abc import Callable -from enum import Enum -import json -import os -from pathlib import Path -import platform -from queue import Queue -import re -import shutil -import socket -import stat -import subprocess # nosec -import sys -import tempfile -from threading import Event -from threading import Thread -import time -from typing import Any -from typing import cast -from urllib.parse import urlparse -import webbrowser - -# third party -import click -import requests -import rich -from rich.console import Console -from rich.live import Live -from rich.progress import BarColumn -from rich.progress import Progress -from rich.progress import SpinnerColumn -from rich.progress import TextColumn -from virtualenvapi.manage import VirtualEnvironment - -# relative -from .art import RichEmoji -from .art import hagrid -from .art import quickstart_art -from .auth import AuthCredentials -from .cache import DEFAULT_BRANCH -from .cache import DEFAULT_REPO -from .cache import arg_cache -from .deps import DEPENDENCIES -from .deps import LATEST_BETA_SYFT -from .deps import allowed_hosts -from .deps import check_docker_service_status -from .deps import check_docker_version -from .deps import check_grid_docker -from .deps import gather_debug -from .deps import get_version_string -from .deps import is_windows -from .exceptions import MissingDependency -from .grammar import BadGrammar -from .grammar import GrammarVerb -from .grammar import parse_grammar -from .land import get_land_verb -from .launch import get_launch_verb -from .lib import GIT_REPO -from .lib import GRID_SRC_PATH -from .lib import GRID_SRC_VERSION -from .lib import check_api_metadata -from .lib import check_host -from .lib import check_jupyter_server -from .lib import check_login_page -from .lib import commit_hash -from .lib import docker_desktop_memory -from .lib import find_available_port -from .lib import generate_process_status_table -from .lib import generate_user_table -from .lib import gitpod_url -from .lib import hagrid_root -from .lib import is_gitpod -from .lib import name_tag -from .lib import save_vm_details_as_json -from .lib import update_repo -from .lib import use_branch -from .mode import EDITABLE_MODE -from .parse_template import deployment_dir -from .parse_template import get_template_yml -from .parse_template import manifest_cache_path -from .parse_template import render_templates -from .parse_template import setup_from_manifest_template -from .quickstart_ui import fetch_notebooks_for_url -from .quickstart_ui import fetch_notebooks_from_zipfile -from .quickstart_ui import quickstart_download_notebook -from .rand_sec import generate_sec_random_password -from .stable_version import LATEST_STABLE_SYFT -from .style import RichGroup -from .util import fix_windows_virtualenv_api -from .util import from_url -from .util import shell - -# fix VirtualEnvironment bug in windows -fix_windows_virtualenv_api(VirtualEnvironment) - - -class NodeSideType(Enum): - LOW_SIDE = "low" - HIGH_SIDE = "high" - - -def get_azure_image(short_name: str) -> str: - prebuild_070 = ( - "madhavajay1632269232059:openmined_mj_grid_domain_ubuntu_1:domain_070:latest" - ) - fresh_ubuntu = "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest" - if short_name == "default": - return fresh_ubuntu - elif short_name == "domain_0.7.0": - return prebuild_070 - raise Exception(f"Image name doesn't exist: {short_name}. Try: default or 0.7.0") - - -@click.group(cls=RichGroup) -def cli() -> None: - pass - - -def get_compose_src_path( - node_name: str, - template_location: str | None = None, - **kwargs: Any, -) -> str: - grid_path = GRID_SRC_PATH() - tag = kwargs["tag"] - # Use local compose files if in editable mode and - # template_location is None and (kwargs["dev"] is True or tag is local) - if ( - EDITABLE_MODE - and template_location is None - and (kwargs["dev"] is True or tag == "local") - ): - path = grid_path - else: - path = deployment_dir(node_name) - - os.makedirs(path, exist_ok=True) - return path - - -@click.command( - help="Restore some part of the hagrid installation or deployment to its initial/starting state.", - context_settings={"show_default": True}, -) -@click.argument("location", type=str, nargs=1) -def clean(location: str) -> None: - if location == "library" or location == "volumes": - print("Deleting all Docker volumes in 2 secs (Ctrl-C to stop)") - time.sleep(2) - subprocess.call("docker volume rm $(docker volume ls -q)", shell=True) # nosec - - if location == "containers" or location == "pantry": - print("Deleting all Docker containers in 2 secs (Ctrl-C to stop)") - time.sleep(2) - subprocess.call("docker rm -f $(docker ps -a -q)", shell=True) # nosec - - if location == "images": - print("Deleting all Docker images in 2 secs (Ctrl-C to stop)") - time.sleep(2) - subprocess.call("docker rmi $(docker images -q)", shell=True) # nosec - - -@click.command( - help="Start a new PyGrid domain/network node!", - context_settings={"show_default": True}, -) -@click.argument("args", type=str, nargs=-1) -@click.option( - "--username", - default=None, - required=False, - type=str, - help="Username for provisioning the remote host", -) -@click.option( - "--key-path", - default=None, - required=False, - type=str, - help="Path to the key file for provisioning the remote host", -) -@click.option( - "--password", - default=None, - required=False, - type=str, - help="Password for provisioning the remote host", -) -@click.option( - "--repo", - default=None, - required=False, - type=str, - help="Repo to fetch source from", -) -@click.option( - "--branch", - default=None, - required=False, - type=str, - help="Branch to monitor for updates", -) -@click.option( - "--tail", - is_flag=True, - help="Tail logs on launch", -) -@click.option( - "--headless", - is_flag=True, - help="Start the frontend container", -) -@click.option( - "--cmd", - is_flag=True, - help="Print the cmd without running it", -) -@click.option( - "--jupyter", - is_flag=True, - help="Enable Jupyter Notebooks", -) -@click.option( - "--in-mem-workers", - is_flag=True, - help="Enable InMemory Workers", -) -@click.option( - "--enable-signup", - is_flag=True, - help="Enable Signup for Node", -) -@click.option( - "--build", - is_flag=True, - help="Disable forcing re-build", -) -@click.option( - "--no-provision", - is_flag=True, - help="Disable provisioning VMs", -) -@click.option( - "--node-count", - default=1, - required=False, - type=click.IntRange(1, 250), - help="Number of independent nodes/VMs to launch", -) -@click.option( - "--auth-type", - default=None, - type=click.Choice(["key", "password"], case_sensitive=False), -) -@click.option( - "--ansible-extras", - default="", - type=str, -) -@click.option("--tls", is_flag=True, help="Launch with TLS configuration") -@click.option("--test", is_flag=True, help="Launch with test configuration") -@click.option("--dev", is_flag=True, help="Shortcut for development mode") -@click.option( - "--release", - default="production", - required=False, - type=click.Choice(["production", "staging", "development"], case_sensitive=False), - help="Choose between production and development release", -) -@click.option( - "--deployment-type", - default="container_stack", - required=False, - type=click.Choice(["container_stack", "single_container"], case_sensitive=False), - help="Choose between container_stack and single_container deployment", -) -@click.option( - "--cert-store-path", - default="/home/om/certs", - required=False, - type=str, - help="Remote path to store and load TLS cert and key", -) -@click.option( - "--upload-tls-cert", - default="", - required=False, - type=str, - help="Local path to TLS cert to upload and store at --cert-store-path", -) -@click.option( - "--upload-tls-key", - default="", - required=False, - type=str, - help="Local path to TLS private key to upload and store at --cert-store-path", -) -@click.option( - "--no-blob-storage", - is_flag=True, - help="Disable blob storage", -) -@click.option( - "--image-name", - default=None, - required=False, - type=str, - help="Image to use for the VM", -) -@click.option( - "--tag", - default=None, - required=False, - type=str, - help="Container image tag to use", -) -@click.option( - "--smtp-username", - default=None, - required=False, - type=str, - help="Username used to auth in email server and enable notification via emails", -) -@click.option( - "--smtp-password", - default=None, - required=False, - type=str, - help="Password used to auth in email server and enable notification via emails", -) -@click.option( - "--smtp-port", - default=None, - required=False, - type=str, - help="Port used by email server to send notification via emails", -) -@click.option( - "--smtp-host", - default=None, - required=False, - type=str, - help="Address used by email server to send notification via emails", -) -@click.option( - "--smtp-sender", - default=None, - required=False, - type=str, - help="Sender email used to deliver PyGrid email notifications.", -) -@click.option( - "--build-src", - default=DEFAULT_BRANCH, - required=False, - type=str, - help="Git branch to use for launch / build operations", -) -@click.option( - "--platform", - default=None, - required=False, - type=str, - help="Run docker with a different platform like linux/arm64", -) -@click.option( - "--verbose", - is_flag=True, - help="Show verbose output", -) -@click.option( - "--trace", - required=False, - type=str, - help="Optional: allow trace to be turned on or off", -) -@click.option( - "--template", - required=False, - default=None, - help="Path or URL to manifest template", -) -@click.option( - "--template-overwrite", - is_flag=True, - help="Force re-downloading of template manifest", -) -@click.option( - "--no-health-checks", - is_flag=True, - help="Turn off auto health checks post node launch", -) -@click.option( - "--set-root-email", - default=None, - required=False, - type=str, - help="Set root email of node", -) -@click.option( - "--set-root-password", - default=None, - required=False, - type=str, - help="Set root password of node", -) -@click.option( - "--azure-resource-group", - default=None, - required=False, - type=str, - help="Azure Resource Group", -) -@click.option( - "--azure-location", - default=None, - required=False, - type=str, - help="Azure Resource Group Location", -) -@click.option( - "--azure-size", - default=None, - required=False, - type=str, - help="Azure VM Size", -) -@click.option( - "--azure-username", - default=None, - required=False, - type=str, - help="Azure VM Username", -) -@click.option( - "--azure-key-path", - default=None, - required=False, - type=str, - help="Azure Key Path", -) -@click.option( - "--azure-repo", - default=None, - required=False, - type=str, - help="Azure Source Repo", -) -@click.option( - "--azure-branch", - default=None, - required=False, - type=str, - help="Azure Source Branch", -) -@click.option( - "--render", - is_flag=True, - help="Render Docker Files", -) -@click.option( - "--no-warnings", - is_flag=True, - help="Enable API warnings on the node.", -) -@click.option( - "--low-side", - is_flag=True, - help="Launch a low side node type else a high side node type", -) -@click.option( - "--set-s3-username", - default=None, - required=False, - type=str, - help="Set root username for s3 blob storage", -) -@click.option( - "--set-s3-password", - default=None, - required=False, - type=str, - help="Set root password for s3 blob storage", -) -@click.option( - "--set-volume-size-limit-mb", - default=1024, - required=False, - type=click.IntRange(1024, 50000), - help="Set the volume size limit (in MBs)", -) -@click.option( - "--association-request-auto-approval", - is_flag=True, - help="Enable auto approval of association requests", -) -def launch(args: tuple[str], **kwargs: Any) -> None: - verb = get_launch_verb() - try: - grammar = parse_grammar(args=args, verb=verb) - verb.load_grammar(grammar=grammar) - except BadGrammar as e: - print(e) - return - - node_name = verb.get_named_term_type(name="node_name") - snake_name = str(node_name.snake_input) - node_type = verb.get_named_term_type(name="node_type") - - # For enclave currently it is only a single container deployment - # This would change when we have side car containers to enclave - if node_type.input == "enclave": - kwargs["deployment_type"] = "single_container" - - compose_src_path = get_compose_src_path( - node_type=node_type, - node_name=snake_name, - template_location=kwargs["template"], - **kwargs, - ) - kwargs["compose_src_path"] = compose_src_path - - try: - update_repo(repo=GIT_REPO(), branch=str(kwargs["build_src"])) - except Exception as e: - print(f"Failed to update repo. {e}") - try: - cmds = create_launch_cmd(verb=verb, kwargs=kwargs) - cmds = [cmds] if isinstance(cmds, str) else cmds - except Exception as e: - print(f"Error: {e}\n\n") - return - - dry_run = bool(kwargs["cmd"]) - - health_checks = not bool(kwargs["no_health_checks"]) - render_only = bool(kwargs["render"]) - - try: - tail = bool(kwargs["tail"]) - verbose = bool(kwargs["verbose"]) - silent = not verbose - if tail: - silent = False - - if render_only: - print( - "Docker Compose Files Rendered: {}".format(kwargs["compose_src_path"]) - ) - return - - execute_commands( - cmds, - dry_run=dry_run, - silent=silent, - compose_src_path=kwargs["compose_src_path"], - node_type=node_type.input, - ) - - host_term = verb.get_named_term_hostgrammar(name="host") - run_health_checks = ( - health_checks and not dry_run and host_term.host == "docker" and silent - ) - - if run_health_checks: - docker_cmds = cast(dict[str, list[str]], cmds) - - # get the first command (cmd1) from docker_cmds which is of the form - # {"": [cmd1, cmd2], "": [cmd3, cmd4]} - (command, *_), *_ = docker_cmds.values() - - match_port = re.search("HTTP_PORT=[0-9]{1,5}", command) - if match_port: - rich.get_console().print( - "\n[bold green]β ‹[bold blue] Checking node API [/bold blue]\t" - ) - port = match_port.group().replace("HTTP_PORT=", "") - - check_status("localhost" + ":" + port, node_name=node_name.snake_input) - - rich.get_console().print( - rich.panel.Panel.fit( - f"✨ To view container logs run [bold green]hagrid logs {node_name.snake_input}[/bold green]\t" - ) - ) - - except Exception as e: - print(f"Error: {e}\n\n") - return - - -def check_errors( - line: str, process: subprocess.Popen, cmd_name: str, progress_bar: Progress -) -> None: - task = progress_bar.tasks[0] - if "Error response from daemon: " in line: - if progress_bar: - progress_bar.update( - 0, - description=f"❌ [bold red]{cmd_name}[/bold red] [{task.completed} / {task.total}]", - refresh=True, - ) - progress_bar.update(0, visible=False) - progress_bar.console.clear_live() - progress_bar.console.quiet = True - progress_bar.stop() - console = rich.get_console() - progress_bar.console.quiet = False - console.print(f"\n\n [red] ERROR [/red]: [bold]{line}[/bold]\n") - process.terminate() - raise Exception - - -def check_pulling(line: str, cmd_name: str, progress_bar: Progress) -> None: - task = progress_bar.tasks[0] - if "Pulling" in line and "fs layer" not in line: - progress_bar.update( - 0, - description=f"[bold]{cmd_name} [{task.completed} / {task.total+1}]", - total=task.total + 1, - refresh=True, - ) - if "Pulled" in line: - progress_bar.update( - 0, - description=f"[bold]{cmd_name} [{task.completed + 1} / {task.total}]", - completed=task.completed + 1, - refresh=True, - ) - if progress_bar.finished: - progress_bar.update( - 0, - description=f"βœ… [bold green]{cmd_name} [{task.completed} / {task.total}]", - refresh=True, - ) - - -def check_building(line: str, cmd_name: str, progress_bar: Progress) -> None: - load_pattern = re.compile( - r"^#.* load build definition from [A-Za-z0-9]+\.dockerfile$", re.IGNORECASE - ) - build_pattern = re.compile( - r"^#.* naming to docker\.io/openmined/.* done$", re.IGNORECASE - ) - task = progress_bar.tasks[0] - - if load_pattern.match(line): - progress_bar.update( - 0, - description=f"[bold]{cmd_name} [{task.completed} / {task.total +1}]", - total=task.total + 1, - refresh=True, - ) - if build_pattern.match(line): - progress_bar.update( - 0, - description=f"[bold]{cmd_name} [{task.completed+1} / {task.total}]", - completed=task.completed + 1, - refresh=True, - ) - - if progress_bar.finished: - progress_bar.update( - 0, - description=f"βœ… [bold green]{cmd_name} [{task.completed} / {task.total}]", - refresh=True, - ) - - -def check_launching(line: str, cmd_name: str, progress_bar: Progress) -> None: - task = progress_bar.tasks[0] - if "Starting" in line: - progress_bar.update( - 0, - description=f" [bold]{cmd_name} [{task.completed} / {task.total+1}]", - total=task.total + 1, - refresh=True, - ) - if "Started" in line: - progress_bar.update( - 0, - description=f" [bold]{cmd_name} [{task.completed + 1} / {task.total}]", - completed=task.completed + 1, - refresh=True, - ) - if progress_bar.finished: - progress_bar.update( - 0, - description=f"βœ… [bold green]{cmd_name} [{task.completed} / {task.total}]", - refresh=True, - ) - - -DOCKER_FUNC_MAP = { - "Pulling": check_pulling, - "Building": check_building, - "Launching": check_launching, -} - - -def read_thread_logs( - progress_bar: Progress, process: subprocess.Popen, queue: Queue, cmd_name: str -) -> None: - line = queue.get() - line = str(line, encoding="utf-8").strip() - - if progress_bar: - check_errors(line, process, cmd_name, progress_bar=progress_bar) - DOCKER_FUNC_MAP[cmd_name](line, cmd_name, progress_bar=progress_bar) - - -def create_thread_logs(process: subprocess.Popen) -> Queue: - def enqueue_output(out: Any, queue: Queue) -> None: - for line in iter(out.readline, b""): - queue.put(line) - out.close() - - queue: Queue = Queue() - thread_1 = Thread(target=enqueue_output, args=(process.stdout, queue)) - thread_2 = Thread(target=enqueue_output, args=(process.stderr, queue)) - - thread_1.daemon = True # thread dies with the program - thread_1.start() - thread_2.daemon = True # thread dies with the program - thread_2.start() - return queue - - -def process_cmd( - cmds: list[str], - node_type: str, - dry_run: bool, - silent: bool, - compose_src_path: str, - progress_bar: Progress | None = None, - cmd_name: str = "", -) -> None: - process_list: list = [] - cwd = compose_src_path - - username, password = ( - extract_username_and_pass(cmds[0]) if len(cmds) > 0 else ("-", "-") - ) - # display VM credentials - console = rich.get_console() - credentials = generate_user_table(username=username, password=password) - if credentials: - console.print(credentials) - - for cmd in cmds: - if dry_run: - print(f"\nRunning:\ncd {cwd}\n", hide_password(cmd=cmd)) - continue - - # use powershell if environment is Windows - cmd_to_exec = ["powershell.exe", "-Command", cmd] if is_windows() else cmd - - try: - if len(cmds) > 1: - process = subprocess.Popen( # nosec - cmd_to_exec, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=cwd, - shell=True, - ) - ip_address = extract_host_ip_from_cmd(cmd) - jupyter_token = extract_jupyter_token(cmd) - process_list.append((ip_address, process, jupyter_token)) - else: - display_jupyter_token(cmd) - if silent: - ON_POSIX = "posix" in sys.builtin_module_names - - process = subprocess.Popen( # nosec - cmd_to_exec, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=cwd, - close_fds=ON_POSIX, - shell=True, - ) - - # Creates two threads to get docker stdout and sterr - logs_queue = create_thread_logs(process=process) - - read_thread_logs(progress_bar, process, logs_queue, cmd_name) - while process.poll() != 0: - while not logs_queue.empty(): - # Read stdout and sterr to check errors or update progress bar. - read_thread_logs( - progress_bar, process, logs_queue, cmd_name - ) - else: - if progress_bar: - progress_bar.stop() - - subprocess.run( # nosec - cmd_to_exec, - shell=True, - cwd=cwd, - ) - except Exception as e: - print(f"Failed to run cmd: {cmd}. {e}") - - if dry_run is False and len(process_list) > 0: - # display VM launch status - display_vm_status(process_list) - - # save vm details as json - save_vm_details_as_json(username, password, process_list) - - -def execute_commands( - cmds: list[str] | dict[str, list[str]], - node_type: str, - compose_src_path: str, - dry_run: bool = False, - silent: bool = False, -) -> None: - """Execute the launch commands and display their status in realtime. - - Args: - cmds (list): list of commands to be executed - dry_run (bool, optional): If `True` only displays cmds to be executed. Defaults to False. - """ - console = rich.get_console() - if isinstance(cmds, dict): - console.print("[bold green]β ‹[bold blue] Launching Containers [/bold blue]\t") - for cmd_name, cmd in cmds.items(): - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:.2f}% "), - console=console, - auto_refresh=True, - ) as progress: - if silent: - progress.add_task( - f"[bold green]{cmd_name} Images", - total=0, - ) - process_cmd( - cmds=cmd, - node_type=node_type, - dry_run=dry_run, - silent=silent, - compose_src_path=compose_src_path, - progress_bar=progress, - cmd_name=cmd_name, - ) - else: - process_cmd( - cmds=cmds, - node_type=node_type, - dry_run=dry_run, - silent=silent, - compose_src_path=compose_src_path, - ) - - -def display_vm_status(process_list: list) -> None: - """Display the status of the processes being executed on the VM. - - Args: - process_list (list): list of processes executed. - """ - - # Generate the table showing the status of each process being executed - status_table, process_completed = generate_process_status_table(process_list) - - # Render the live table - with Live(status_table, refresh_per_second=1) as live: - # Loop till all processes have not completed executing - while not process_completed: - status_table, process_completed = generate_process_status_table( - process_list - ) - live.update(status_table) # Update the process status table - - -def display_jupyter_token(cmd: str) -> None: - token = extract_jupyter_token(cmd=cmd) - if token is not None: - print(f"Jupyter Token: {token}") - - -def extract_username_and_pass(cmd: str) -> tuple: - # Extract username - matcher = r"--user (.+?) " - username = re.findall(matcher, cmd) - username = username[0] if len(username) > 0 else None - - # Extract password - matcher = r"ansible_ssh_pass='(.+?)'" - password = re.findall(matcher, cmd) - password = password[0] if len(password) > 0 else None - - return username, password - - -def extract_jupyter_token(cmd: str) -> str | None: - matcher = r"jupyter_token='(.+?)'" - token = re.findall(matcher, cmd) - if len(token) == 1: - return token[0] - return None - - -def hide_password(cmd: str) -> str: - try: - matcher = r"ansible_ssh_pass='(.+?)'" - passwords = re.findall(matcher, cmd) - if len(passwords) > 0: - password = passwords[0] - stars = "*" * 4 - cmd = cmd.replace( - f"ansible_ssh_pass='{password}'", f"ansible_ssh_pass='{stars}'" - ) - return cmd - except Exception as e: - print("Failed to hide password.") - raise e - - -def hide_azure_vm_password(azure_cmd: str) -> str: - try: - matcher = r"admin-password '(.+?)'" - passwords = re.findall(matcher, azure_cmd) - if len(passwords) > 0: - password = passwords[0] - stars = "*" * 4 - azure_cmd = azure_cmd.replace( - f"admin-password '{password}'", f"admin-password '{stars}'" - ) - return azure_cmd - except Exception as e: - print("Failed to hide password.") - raise e - - -class QuestionInputError(Exception): - pass - - -class QuestionInputPathError(Exception): - pass - - -class Question: - def __init__( - self, - var_name: str, - question: str, - kind: str, - default: str | None = None, - cache: bool = False, - options: list[str] | None = None, - ) -> None: - self.var_name = var_name - self.question = question - self.default = default - self.kind = kind - self.cache = cache - self.options = options if options is not None else [] - - def validate(self, value: str) -> str: - value = value.strip() - if self.default is not None and value == "": - return self.default - - if self.kind == "path": - value = os.path.expanduser(value) - if not os.path.exists(value): - error = f"{value} is not a valid path." - if self.default is not None: - error += f" Try {self.default}" - raise QuestionInputPathError(f"{error}") - - if self.kind == "yesno": - if value.lower().startswith("y"): - return "y" - elif value.lower().startswith("n"): - return "n" - else: - raise QuestionInputError(f"{value} is not an yes or no answer") - - if self.kind == "options": - if value in self.options: - return value - first_letter = value.lower()[0] - for option in self.options: - if option.startswith(first_letter): - return option - - raise QuestionInputError( - f"{value} is not one of the options: {self.options}" - ) - - if self.kind == "password": - try: - return validate_password(password=value) - except Exception as e: - raise QuestionInputError(f"Invalid password. {e}") - return value - - -def ask(question: Question, kwargs: dict[str, str]) -> str: - if question.var_name in kwargs and kwargs[question.var_name] is not None: - value = kwargs[question.var_name] - else: - if question.default is not None: - value = click.prompt(question.question, type=str, default=question.default) - elif question.var_name == "password": - value = click.prompt( - question.question, type=str, hide_input=True, confirmation_prompt=True - ) - else: - value = click.prompt(question.question, type=str) - - try: - value = question.validate(value=value) - except QuestionInputError as e: - print(e) - return ask(question=question, kwargs=kwargs) - if question.cache: - arg_cache[question.var_name] = value - - return value - - -def fix_key_permission(private_key_path: str) -> None: - key_permission = oct(stat.S_IMODE(os.stat(private_key_path).st_mode)) - chmod_permission = "400" - octal_permission = f"0o{chmod_permission}" - if key_permission != octal_permission: - print( - f"Fixing key permission: {private_key_path}, setting to {chmod_permission}" - ) - try: - os.chmod(private_key_path, int(octal_permission, 8)) - except Exception as e: - print("Failed to fix key permission", e) - raise e - - -def private_to_public_key(private_key_path: str, temp_path: str, username: str) -> str: - # check key permission - fix_key_permission(private_key_path=private_key_path) - output_path = f"{temp_path}/hagrid_{username}_key.pub" - cmd = f"ssh-keygen -f {private_key_path} -y > {output_path}" - try: - subprocess.check_call(cmd, shell=True) # nosec - except Exception as e: - print("failed to make ssh key", e) - raise e - return output_path - - -def check_azure_authed() -> bool: - cmd = "az account show" - try: - subprocess.check_call(cmd, shell=True, stdout=subprocess.DEVNULL) # nosec - return True - except Exception: # nosec - pass - return False - - -def login_azure() -> bool: - cmd = "az login" - try: - subprocess.check_call(cmd, shell=True, stdout=subprocess.DEVNULL) # nosec - return True - except Exception: # nosec - pass - return False - - -def check_azure_cli_installed() -> bool: - try: - result = subprocess.run( # nosec - ["az", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT - ) - if result.returncode != 0: - raise FileNotFoundError("az not installed") - except Exception: # nosec - msg = "\nYou don't appear to have the Azure CLI installed!!! \n\n\ -Please install it and then retry your command.\ -\n\nInstallation Instructions: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli\n" - raise FileNotFoundError(msg) - - return True - - -def check_gcloud_cli_installed() -> bool: - try: - subprocess.call(["gcloud", "version"]) # nosec - print("Gcloud cli installed!") - except FileNotFoundError: - msg = "\nYou don't appear to have the gcloud CLI tool installed! \n\n\ -Please install it and then retry again.\ -\n\nInstallation Instructions: https://cloud.google.com/sdk/docs/install-sdk \n" - raise FileNotFoundError(msg) - - return True - - -def check_aws_cli_installed() -> bool: - try: - result = subprocess.run( # nosec - ["aws", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT - ) - if result.returncode != 0: - raise FileNotFoundError("AWS CLI not installed") - except Exception: # nosec - msg = "\nYou don't appear to have the AWS CLI installed! \n\n\ -Please install it and then retry your command.\ -\n\nInstallation Instructions: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html\n" - raise FileNotFoundError(msg) - - return True - - -def check_gcloud_authed() -> bool: - try: - result = subprocess.run( # nosec - ["gcloud", "auth", "print-identity-token"], stdout=subprocess.PIPE - ) - if result.returncode == 0: - return True - except Exception: # nosec - pass - return False - - -def login_gcloud() -> bool: - cmd = "gcloud auth login" - try: - subprocess.check_call(cmd, shell=True, stdout=subprocess.DEVNULL) # nosec - return True - except Exception: # nosec - pass - return False - - -def str_to_bool(bool_str: str | None) -> bool: - result = False - bool_str = str(bool_str).lower() - if bool_str == "true" or bool_str == "1": - result = True - return result - - -ART = str_to_bool(os.environ.get("HAGRID_ART", "True")) - - -def generate_gcloud_key_at_path(key_path: str) -> str: - key_path = os.path.expanduser(key_path) - if os.path.exists(key_path): - raise Exception(f"Can't generate key since path already exists. {key_path}") - else: - # triggers a key check - cmd = "gcloud compute ssh '' --dry-run" - try: - subprocess.check_call(cmd, shell=True) # nosec - except Exception: # nosec - pass - if not os.path.exists(key_path): - raise Exception(f"gcloud failed to generate ssh-key at: {key_path}") - - return key_path - - -def generate_aws_key_at_path(key_path: str, key_name: str) -> str: - key_path = os.path.expanduser(key_path) - if os.path.exists(key_path): - raise Exception(f"Can't generate key since path already exists. {key_path}") - else: - # TODO we need to do differently for powershell. - # Ex: aws ec2 create-key-pair --key-name MyKeyPair --query 'KeyMaterial' - # --output text | out-file -encoding ascii -filepath MyKeyPair.pem - - print(f"Creating AWS key pair with name {key_name} at path {key_path}..") - cmd = f"aws ec2 create-key-pair --key-name {key_name} --query 'KeyMaterial' --output text > {key_path}" - try: - subprocess.check_call(cmd, shell=True) # nosec - subprocess.check_call(f"chmod 400 {key_path}", shell=True) # nosec - except Exception as e: # nosec - print(f"Failed to create key: {e}") - if not os.path.exists(key_path): - raise Exception(f"AWS failed to generate key pair at: {key_path}") - - return key_path - - -def generate_key_at_path(key_path: str) -> str: - key_path = os.path.expanduser(key_path) - if os.path.exists(key_path): - raise Exception(f"Can't generate key since path already exists. {key_path}") - else: - cmd = f"ssh-keygen -N '' -f {key_path}" - try: - subprocess.check_call(cmd, shell=True) # nosec - if not os.path.exists(key_path): - raise Exception(f"Failed to generate ssh-key at: {key_path}") - except Exception as e: - raise e - - return key_path - - -def validate_password(password: str) -> str: - """Validate if the password entered by the user is valid. - - Password length should be between 12 - 123 characters - Passwords must also meet 3 out of the following 4 complexity requirements: - - Have lower characters - - Have upper characters - - Have a digit - - Have a special character - - Args: - password (str): password for the vm - - Returns: - str: password if it is valid - """ - # Validate password length - if len(password) < 12 or len(password) > 123: - raise ValueError("Password length should be between 12 - 123 characters") - - # Valid character types - character_types = { - "upper_case": False, - "lower_case": False, - "digit": False, - "special": False, - } - - for ch in password: - if ch.islower(): - character_types["lower_case"] = True - elif ch.isupper(): - character_types["upper_case"] = True - elif ch.isdigit(): - character_types["digit"] = True - elif ch.isascii(): - character_types["special"] = True - else: - raise ValueError(f"{ch} is not a valid character for password") - - # Validate characters in the password - required_character_type_count = sum( - [int(value) for value in character_types.values()] - ) - - if required_character_type_count >= 3: - return password - - absent_character_types = ", ".join( - char_type for char_type, value in character_types.items() if value is False - ).strip(", ") - - raise ValueError( - f"At least one {absent_character_types} character types must be present" - ) - - -def create_launch_cmd( - verb: GrammarVerb, - kwargs: dict[str, Any], - ignore_docker_version_check: bool | None = False, -) -> str | list[str] | dict[str, list[str]]: - parsed_kwargs: dict[str, Any] = {} - host_term = verb.get_named_term_hostgrammar(name="host") - - host = host_term.host - auth: AuthCredentials | None = None - - tail = bool(kwargs["tail"]) - - parsed_kwargs = {} - - parsed_kwargs["build"] = bool(kwargs["build"]) - - parsed_kwargs["use_blob_storage"] = not bool(kwargs["no_blob_storage"]) - - parsed_kwargs["in_mem_workers"] = bool(kwargs["in_mem_workers"]) - - if parsed_kwargs["use_blob_storage"]: - parsed_kwargs["set_s3_username"] = kwargs["set_s3_username"] - parsed_kwargs["set_s3_password"] = kwargs["set_s3_password"] - parsed_kwargs["set_volume_size_limit_mb"] = kwargs["set_volume_size_limit_mb"] - - parsed_kwargs["association_request_auto_approval"] = str( - kwargs["association_request_auto_approval"] - ) - - parsed_kwargs["node_count"] = ( - int(kwargs["node_count"]) if "node_count" in kwargs else 1 - ) - - if parsed_kwargs["node_count"] > 1 and host not in ["azure"]: - print("\nArgument `node_count` is only supported with `azure`.\n") - else: - # Default to detached mode if running more than one nodes - tail = False if parsed_kwargs["node_count"] > 1 else tail - - headless = bool(kwargs["headless"]) - parsed_kwargs["headless"] = headless - - parsed_kwargs["tls"] = bool(kwargs["tls"]) - parsed_kwargs["test"] = bool(kwargs["test"]) - parsed_kwargs["dev"] = bool(kwargs["dev"]) - - parsed_kwargs["silent"] = not bool(kwargs["verbose"]) - - parsed_kwargs["trace"] = False - if ("trace" not in kwargs or kwargs["trace"] is None) and parsed_kwargs["dev"]: - # default to trace on in dev mode - parsed_kwargs["trace"] = False - elif "trace" in kwargs: - parsed_kwargs["trace"] = str_to_bool(cast(str, kwargs["trace"])) - - parsed_kwargs["release"] = "production" - if "release" in kwargs and kwargs["release"] != "production": - parsed_kwargs["release"] = kwargs["release"] - - # if we use --dev override it - if parsed_kwargs["dev"] is True: - parsed_kwargs["release"] = "development" - - # derive node type - if kwargs["low_side"]: - parsed_kwargs["node_side_type"] = NodeSideType.LOW_SIDE.value - else: - parsed_kwargs["node_side_type"] = NodeSideType.HIGH_SIDE.value - - parsed_kwargs["smtp_username"] = kwargs["smtp_username"] - parsed_kwargs["smtp_password"] = kwargs["smtp_password"] - parsed_kwargs["smtp_port"] = kwargs["smtp_port"] - parsed_kwargs["smtp_host"] = kwargs["smtp_host"] - parsed_kwargs["smtp_sender"] = kwargs["smtp_sender"] - - parsed_kwargs["enable_warnings"] = not kwargs["no_warnings"] - - # choosing deployment type - parsed_kwargs["deployment_type"] = "container_stack" - if "deployment_type" in kwargs and kwargs["deployment_type"] is not None: - parsed_kwargs["deployment_type"] = kwargs["deployment_type"] - - if "cert_store_path" in kwargs: - parsed_kwargs["cert_store_path"] = kwargs["cert_store_path"] - if "upload_tls_cert" in kwargs: - parsed_kwargs["upload_tls_cert"] = kwargs["upload_tls_cert"] - if "upload_tls_key" in kwargs: - parsed_kwargs["upload_tls_key"] = kwargs["upload_tls_key"] - - parsed_kwargs["provision"] = not bool(kwargs["no_provision"]) - - if "image_name" in kwargs and kwargs["image_name"] is not None: - parsed_kwargs["image_name"] = kwargs["image_name"] - else: - parsed_kwargs["image_name"] = "default" - - if parsed_kwargs["dev"] is True: - parsed_kwargs["tag"] = "local" - else: - if "tag" in kwargs and kwargs["tag"] is not None and kwargs["tag"] != "": - parsed_kwargs["tag"] = kwargs["tag"] - else: - parsed_kwargs["tag"] = "latest" - - if "jupyter" in kwargs and kwargs["jupyter"] is not None: - parsed_kwargs["jupyter"] = str_to_bool(cast(str, kwargs["jupyter"])) - else: - parsed_kwargs["jupyter"] = False - - # allows changing docker platform to other cpu architectures like arm64 - parsed_kwargs["platform"] = kwargs["platform"] if "platform" in kwargs else None - - parsed_kwargs["tail"] = tail - - parsed_kwargs["set_root_password"] = ( - kwargs["set_root_password"] if "set_root_password" in kwargs else None - ) - - parsed_kwargs["set_root_email"] = ( - kwargs["set_root_email"] if "set_root_email" in kwargs else None - ) - - parsed_kwargs["template"] = kwargs["template"] if "template" in kwargs else None - parsed_kwargs["template_overwrite"] = bool(kwargs["template_overwrite"]) - - parsed_kwargs["compose_src_path"] = kwargs["compose_src_path"] - - parsed_kwargs["enable_signup"] = str_to_bool(cast(str, kwargs["enable_signup"])) - - # Override template tag with user input tag - if ( - parsed_kwargs["tag"] is not None - and parsed_kwargs["template"] is None - and parsed_kwargs["tag"] not in ["local"] - ): - # third party - from packaging import version - - pattern = r"[0-9].[0-9].[0-9]" - input_tag = parsed_kwargs["tag"] - if ( - not re.match(pattern, input_tag) - and input_tag != "latest" - and input_tag != "beta" - and "b" not in input_tag - ): - raise Exception( - f"Not a valid tag: {parsed_kwargs['tag']}" - + "\nValid tags: latest, beta, beta version(ex: 0.8.2b35),[0-9].[0-9].[0-9]" - ) - - # TODO: we need to redo this so that pypi and docker mappings are in a single - # file inside dev - if parsed_kwargs["tag"] == "latest": - parsed_kwargs["template"] = LATEST_STABLE_SYFT - parsed_kwargs["tag"] = LATEST_STABLE_SYFT - elif parsed_kwargs["tag"] == "beta" or "b" in parsed_kwargs["tag"]: - tag = ( - LATEST_BETA_SYFT - if parsed_kwargs["tag"] == "beta" - else parsed_kwargs["tag"] - ) - - # Currently, manifest_template.yml is only supported for beta versions >= 0.8.2b34 - beta_version = version.parse(tag) - MINIMUM_BETA_VERSION = "0.8.2b34" - if beta_version < version.parse(MINIMUM_BETA_VERSION): - raise Exception( - f"Minimum beta version tag supported is {MINIMUM_BETA_VERSION}" - ) - - # Check if the beta version is available - template_url = f"https://github.com/OpenMined/PySyft/releases/download/v{str(beta_version)}/manifest_template.yml" - response = requests.get(template_url) # nosec - if response.status_code != 200: - raise Exception( - f"Tag {parsed_kwargs['tag']} is not available" - + " \n for download. Please check the available tags at: " - + "\n https://github.com/OpenMined/PySyft/releases" - ) - - parsed_kwargs["template"] = template_url - parsed_kwargs["tag"] = tag - else: - MINIMUM_TAG_VERSION = version.parse("0.8.0") - tag = version.parse(parsed_kwargs["tag"]) - if tag < MINIMUM_TAG_VERSION: - raise Exception( - f"Minimum supported stable tag version is {MINIMUM_TAG_VERSION}" - ) - parsed_kwargs["template"] = parsed_kwargs["tag"] - - if host in ["docker"] and parsed_kwargs["template"] and host is not None: - # Setup the files from the manifest_template.yml - kwargs = setup_from_manifest_template( - host_type=host, - deployment_type=parsed_kwargs["deployment_type"], - template_location=parsed_kwargs["template"], - overwrite=parsed_kwargs["template_overwrite"], - verbose=kwargs["verbose"], - ) - - parsed_kwargs.update(kwargs) - - if host in ["docker"]: - # Check docker service status - if not ignore_docker_version_check: - check_docker_service_status() - - # Check grid docker versions - if not ignore_docker_version_check: - check_grid_docker(display=True, output_in_text=True) - - if not ignore_docker_version_check: - version = check_docker_version() - else: - version = "n/a" - - if version: - # If the user is using docker desktop (OSX/Windows), check to make sure there's enough RAM. - # If the user is using Linux this isn't an issue because Docker scales to the avaialble RAM, - # but on Docker Desktop it defaults to 2GB which isn't enough. - dd_memory = docker_desktop_memory() - if dd_memory < 8192 and dd_memory != -1: - raise Exception( - "You appear to be using Docker Desktop but don't have " - "enough memory allocated. It appears you've configured " - f"Memory:{dd_memory} MB when 8192MB (8GB) is required. " - f"Please open Docker Desktop Preferences panel and set Memory" - f" to 8GB or higher. \n\n" - f"\tOSX Help: https://docs.docker.com/desktop/mac/\n" - f"\tWindows Help: https://docs.docker.com/desktop/windows/\n\n" - f"Then re-run your hagrid command.\n\n" - f"If you see this warning on Linux then something isn't right. " - f"Please file a Github Issue on PySyft's Github.\n\n" - f"Alternatively in case no more memory could be allocated, " - f"you can run hagrid on the cloud with GitPod by visiting " - f"https://gitpod.io/#https://github.com/OpenMined/PySyft." - ) - - if is_windows() and not DEPENDENCIES["wsl"]: - raise Exception( - "You must install wsl2 for Windows to use HAGrid.\n" - "In PowerShell or Command Prompt type:\n> wsl --install\n\n" - "Read more here: https://docs.microsoft.com/en-us/windows/wsl/install" - ) - - return create_launch_docker_cmd( - verb=verb, - docker_version=version, - tail=tail, - kwargs=parsed_kwargs, - silent=parsed_kwargs["silent"], - ) - - elif host in ["azure"]: - check_azure_cli_installed() - - while not check_azure_authed(): - print("You need to log into Azure") - login_azure() - - if DEPENDENCIES["ansible-playbook"]: - resource_group = ask( - question=Question( - var_name="azure_resource_group", - question="What resource group name do you want to use (or create)?", - default=arg_cache["azure_resource_group"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - location = ask( - question=Question( - var_name="azure_location", - question="If this is a new resource group what location?", - default=arg_cache["azure_location"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - size = ask( - question=Question( - var_name="azure_size", - question="What size machine?", - default=arg_cache["azure_size"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - username = ask( - question=Question( - var_name="azure_username", - question="What do you want the username for the VM to be?", - default=arg_cache["azure_username"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - parsed_kwargs["auth_type"] = ask( - question=Question( - var_name="auth_type", - question="Do you want to login with a key or password", - default=arg_cache["auth_type"], - kind="option", - options=["key", "password"], - cache=True, - ), - kwargs=kwargs, - ) - - key_path = None - if parsed_kwargs["auth_type"] == "key": - key_path_question = Question( - var_name="azure_key_path", - question=f"Absolute path of the private key to access {username}@{host}?", - default=arg_cache["azure_key_path"], - kind="path", - cache=True, - ) - try: - key_path = ask( - key_path_question, - kwargs=kwargs, - ) - except QuestionInputPathError as e: - print(e) - key_path = str(e).split("is not a valid path")[0].strip() - - create_key_question = Question( - var_name="azure_key_path", - question=f"Key {key_path} does not exist. Do you want to create it? (y/n)", - default="y", - kind="yesno", - ) - create_key = ask( - create_key_question, - kwargs=kwargs, - ) - if create_key == "y": - key_path = generate_key_at_path(key_path=key_path) - else: - raise QuestionInputError( - "Unable to create VM without a private key" - ) - elif parsed_kwargs["auth_type"] == "password": - auto_generate_password = ask( - question=Question( - var_name="auto_generate_password", - question="Do you want to auto-generate the password? (y/n)", - kind="yesno", - ), - kwargs=kwargs, - ) - if auto_generate_password == "y": # nosec - parsed_kwargs["password"] = generate_sec_random_password(length=16) - elif auto_generate_password == "n": # nosec - parsed_kwargs["password"] = ask( - question=Question( - var_name="password", - question=f"Password for {username}@{host}?", - kind="password", - ), - kwargs=kwargs, - ) - - repo = ask( - Question( - var_name="azure_repo", - question="Repo to fetch source from?", - default=arg_cache["azure_repo"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - branch = ask( - Question( - var_name="azure_branch", - question="Branch to monitor for updates?", - default=arg_cache["azure_branch"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - use_branch(branch=branch) - - password = parsed_kwargs.get("password") - - auth = AuthCredentials( - username=username, key_path=key_path, password=password - ) - - if not auth.valid: - raise Exception(f"Login Credentials are not valid. {auth}") - - return create_launch_azure_cmd( - verb=verb, - resource_group=resource_group, - location=location, - size=size, - username=username, - password=password, - key_path=key_path, - repo=repo, - branch=branch, - auth=auth, - ansible_extras=kwargs["ansible_extras"], - kwargs=parsed_kwargs, - ) - else: - errors = [] - if not DEPENDENCIES["ansible-playbook"]: - errors.append("ansible-playbook") - msg = "\nERROR!!! MISSING DEPENDENCY!!!" - msg += f"\n\nLaunching a Cloud VM requires: {' '.join(errors)}" - msg += "\n\nPlease follow installation instructions: " - msg += "https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#" - msg += "\n\nNote: we've found the 'conda' based installation instructions to work best" - msg += " (e.g. something lke 'conda install -c conda-forge ansible'). " - msg += "The pip based instructions seem to be a bit buggy if you're using a conda environment" - msg += "\n" - raise MissingDependency(msg) - - elif host in ["gcp"]: - check_gcloud_cli_installed() - - while not check_gcloud_authed(): - print("You need to log into Google Cloud") - login_gcloud() - - if DEPENDENCIES["ansible-playbook"]: - project_id = ask( - question=Question( - var_name="gcp_project_id", - question="What PROJECT ID do you want to use?", - default=arg_cache["gcp_project_id"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - zone = ask( - question=Question( - var_name="gcp_zone", - question="What zone do you want your VM in?", - default=arg_cache["gcp_zone"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - machine_type = ask( - question=Question( - var_name="gcp_machine_type", - question="What size machine?", - default=arg_cache["gcp_machine_type"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - username = ask( - question=Question( - var_name="gcp_username", - question="What is your shell username?", - default=arg_cache["gcp_username"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - key_path_question = Question( - var_name="gcp_key_path", - question=f"Private key to access user@{host}?", - default=arg_cache["gcp_key_path"], - kind="path", - cache=True, - ) - try: - key_path = ask( - key_path_question, - kwargs=kwargs, - ) - except QuestionInputPathError as e: - print(e) - key_path = str(e).split("is not a valid path")[0].strip() - - create_key_question = Question( - var_name="gcp_key_path", - question=f"Key {key_path} does not exist. Do you want gcloud to make it? (y/n)", - default="y", - kind="yesno", - ) - create_key = ask( - create_key_question, - kwargs=kwargs, - ) - if create_key == "y": - key_path = generate_gcloud_key_at_path(key_path=key_path) - else: - raise QuestionInputError( - "Unable to create VM without a private key" - ) - - repo = ask( - Question( - var_name="gcp_repo", - question="Repo to fetch source from?", - default=arg_cache["gcp_repo"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - branch = ask( - Question( - var_name="gcp_branch", - question="Branch to monitor for updates?", - default=arg_cache["gcp_branch"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - use_branch(branch=branch) - - auth = AuthCredentials(username=username, key_path=key_path) - - return create_launch_gcp_cmd( - verb=verb, - project_id=project_id, - zone=zone, - machine_type=machine_type, - repo=repo, - auth=auth, - branch=branch, - ansible_extras=kwargs["ansible_extras"], - kwargs=parsed_kwargs, - ) - else: - errors = [] - if not DEPENDENCIES["ansible-playbook"]: - errors.append("ansible-playbook") - msg = "\nERROR!!! MISSING DEPENDENCY!!!" - msg += f"\n\nLaunching a Cloud VM requires: {' '.join(errors)}" - msg += "\n\nPlease follow installation instructions: " - msg += "https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#" - msg += "\n\nNote: we've found the 'conda' based installation instructions to work best" - msg += " (e.g. something lke 'conda install -c conda-forge ansible'). " - msg += "The pip based instructions seem to be a bit buggy if you're using a conda environment" - msg += "\n" - raise MissingDependency(msg) - - elif host in ["aws"]: - check_aws_cli_installed() - - if DEPENDENCIES["ansible-playbook"]: - aws_region = ask( - question=Question( - var_name="aws_region", - question="In what region do you want to deploy the EC2 instance?", - default=arg_cache["aws_region"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - aws_security_group_name = ask( - question=Question( - var_name="aws_security_group_name", - question="Name of the security group to be created?", - default=arg_cache["aws_security_group_name"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - aws_security_group_cidr = ask( - question=Question( - var_name="aws_security_group_cidr", - question="What IP addresses to allow for incoming network traffic? Please use CIDR notation", - default=arg_cache["aws_security_group_cidr"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - ec2_instance_type = ask( - question=Question( - var_name="aws_ec2_instance_type", - question="What EC2 instance type do you want to deploy?", - default=arg_cache["aws_ec2_instance_type"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - aws_key_name = ask( - question=Question( - var_name="aws_key_name", - question="Enter the name of the key pair to use to connect to the EC2 instance", - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - key_path_qn_str = ( - "Please provide the path of the private key to connect to the instance" - ) - key_path_qn_str += " (if it does not exist, this path corresponds to " - key_path_qn_str += "where you want to store the key upon creation)" - key_path_question = Question( - var_name="aws_key_path", - question=key_path_qn_str, - kind="path", - cache=True, - ) - try: - key_path = ask( - key_path_question, - kwargs=kwargs, - ) - except QuestionInputPathError as e: - print(e) - key_path = str(e).split("is not a valid path")[0].strip() - - create_key_question = Question( - var_name="aws_key_path", - question=f"Key {key_path} does not exist. Do you want AWS to make it? (y/n)", - default="y", - kind="yesno", - ) - create_key = ask( - create_key_question, - kwargs=kwargs, - ) - if create_key == "y": - key_path = generate_aws_key_at_path( - key_path=key_path, key_name=aws_key_name - ) - else: - raise QuestionInputError( - "Unable to create EC2 instance without key" - ) - - repo = ask( - Question( - var_name="aws_repo", - question="Repo to fetch source from?", - default=arg_cache["aws_repo"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - branch = ask( - Question( - var_name="aws_branch", - question="Branch to monitor for updates?", - default=arg_cache["aws_branch"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - use_branch(branch=branch) - - username = arg_cache["aws_ec2_instance_username"] - auth = AuthCredentials(username=username, key_path=key_path) - - return create_launch_aws_cmd( - verb=verb, - region=aws_region, - ec2_instance_type=ec2_instance_type, - security_group_name=aws_security_group_name, - aws_security_group_cidr=aws_security_group_cidr, - key_path=key_path, - key_name=aws_key_name, - repo=repo, - branch=branch, - ansible_extras=kwargs["ansible_extras"], - kwargs=parsed_kwargs, - ami_id=arg_cache["aws_image_id"], - username=username, - auth=auth, - ) - - else: - errors = [] - if not DEPENDENCIES["ansible-playbook"]: - errors.append("ansible-playbook") - msg = "\nERROR!!! MISSING DEPENDENCY!!!" - msg += f"\n\nLaunching a Cloud VM requires: {' '.join(errors)}" - msg += "\n\nPlease follow installation instructions: " - msg += "https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#" - msg += "\n\nNote: we've found the 'conda' based installation instructions to work best" - msg += " (e.g. something lke 'conda install -c conda-forge ansible'). " - msg += "The pip based instructions seem to be a bit buggy if you're using a conda environment" - msg += "\n" - raise MissingDependency(msg) - else: - if DEPENDENCIES["ansible-playbook"]: - if host != "localhost": - parsed_kwargs["username"] = ask( - question=Question( - var_name="username", - question=f"Username for {host} with sudo privledges?", - default=arg_cache["username"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - parsed_kwargs["auth_type"] = ask( - question=Question( - var_name="auth_type", - question="Do you want to login with a key or password", - default=arg_cache["auth_type"], - kind="option", - options=["key", "password"], - cache=True, - ), - kwargs=kwargs, - ) - if parsed_kwargs["auth_type"] == "key": - parsed_kwargs["key_path"] = ask( - question=Question( - var_name="key_path", - question=f"Private key to access {parsed_kwargs['username']}@{host}?", - default=arg_cache["key_path"], - kind="path", - cache=True, - ), - kwargs=kwargs, - ) - elif parsed_kwargs["auth_type"] == "password": - parsed_kwargs["password"] = ask( - question=Question( - var_name="password", - question=f"Password for {parsed_kwargs['username']}@{host}?", - kind="password", - ), - kwargs=kwargs, - ) - - parsed_kwargs["repo"] = ask( - question=Question( - var_name="repo", - question="Repo to fetch source from?", - default=arg_cache["repo"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - parsed_kwargs["branch"] = ask( - Question( - var_name="branch", - question="Branch to monitor for updates?", - default=arg_cache["branch"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - auth = None - if host != "localhost": - if parsed_kwargs["auth_type"] == "key": - auth = AuthCredentials( - username=parsed_kwargs["username"], - key_path=parsed_kwargs["key_path"], - ) - else: - auth = AuthCredentials( - username=parsed_kwargs["username"], - key_path=parsed_kwargs["password"], - ) - if not auth.valid: - raise Exception(f"Login Credentials are not valid. {auth}") - parsed_kwargs["ansible_extras"] = kwargs["ansible_extras"] - return create_launch_custom_cmd(verb=verb, auth=auth, kwargs=parsed_kwargs) - else: - errors = [] - if not DEPENDENCIES["ansible-playbook"]: - errors.append("ansible-playbook") - raise MissingDependency( - f"Launching a Custom VM requires: {' '.join(errors)}" - ) - - host_options = ", ".join(allowed_hosts) - raise MissingDependency( - f"Launch requires a correct host option, try: {host_options}" - ) - - -def pull_command(cmd: str, kwargs: dict[str, Any]) -> list[str]: - pull_cmd = str(cmd) - if kwargs["release"] == "production": - pull_cmd += " --file docker-compose.yml" - else: - pull_cmd += " --file docker-compose.pull.yml" - pull_cmd += " pull --ignore-pull-failures" # ignore missing version from Dockerhub - return [pull_cmd] - - -def build_command(cmd: str) -> list[str]: - build_cmd = str(cmd) - build_cmd += " --file docker-compose.build.yml" - build_cmd += " build" - return [build_cmd] - - -def deploy_command(cmd: str, tail: bool, dev_mode: bool) -> list[str]: - up_cmd = str(cmd) - up_cmd += " --file docker-compose.dev.yml" if dev_mode else "" - up_cmd += " up" - if not tail: - up_cmd += " -d" - return [up_cmd] - - -def create_launch_docker_cmd( - verb: GrammarVerb, - docker_version: str, - kwargs: dict[str, Any], - tail: bool = True, - silent: bool = False, -) -> dict[str, list[str]]: - host_term = verb.get_named_term_hostgrammar(name="host") - node_name = verb.get_named_term_type(name="node_name") - node_type = verb.get_named_term_type(name="node_type") - - snake_name = str(node_name.snake_input) - tag = name_tag(name=str(node_name.input)) - - if ART and not silent: - hagrid() - - print( - "Launching a PyGrid " - + str(node_type.input).capitalize() - + " node on port " - + str(host_term.free_port) - + "!\n" - ) - - version_string = kwargs["tag"] - version_hash = "dockerhub" - build = kwargs["build"] - - # if in development mode, generate a version_string which is either - # the one you inputed concatenated with -dev or the contents of the VERSION file - version = GRID_SRC_VERSION() - if "release" in kwargs and kwargs["release"] == "development": - # force version to have -dev at the end in dev mode - # during development we can use the latest beta version - if version_string is None: - version_string = version[0] - version_string += "-dev" - version_hash = version[1] - build = True - else: - # whereas if in production mode and tag == "local" use the local VERSION file - # or if its not set somehow, which should never happen, use stable - # otherwise use the kwargs["tag"] from above - - # during production the default would be stable - if version_string == "local": - # this can be used in VMs in production to auto update from src - version_string = version[0] - version_hash = version[1] - build = True - elif version_string is None: - version_string = "latest" - - if platform.uname().machine.lower() in ["x86_64", "amd64"]: - docker_platform = "linux/amd64" - else: - docker_platform = "linux/arm64" - - if "platform" in kwargs and kwargs["platform"] is not None: - docker_platform = kwargs["platform"] - - if kwargs["template"]: - _, template_hash = get_template_yml(kwargs["template"]) - template_dir = manifest_cache_path(template_hash) - template_grid_dir = f"{template_dir}/packages/grid" - else: - template_grid_dir = GRID_SRC_PATH() - - compose_src_path = kwargs["compose_src_path"] - if not compose_src_path: - compose_src_path = get_compose_src_path( - node_type=node_type, - node_name=snake_name, - template_location=kwargs["template"], - **kwargs, - ) - - default_env = f"{template_grid_dir}/default.env" - if not os.path.exists(default_env): - # old path - default_env = f"{template_grid_dir}/.env" - default_envs = {} - with open(default_env) as f: - for line in f.readlines(): - if "=" in line: - parts = line.strip().split("=") - key = parts[0] - value = "" - if len(parts) > 1: - value = parts[1] - default_envs[key] = value - - single_container_mode = kwargs["deployment_type"] == "single_container" - in_mem_workers = kwargs.get("in_mem_workers") - smtp_username = kwargs.get("smtp_username") - smtp_sender = kwargs.get("smtp_sender") - smtp_password = kwargs.get("smtp_password") - smtp_port = kwargs.get("smtp_port") - if smtp_port is None or smtp_port == "": - smtp_port = int(default_envs["SMTP_PORT"]) - smtp_host = kwargs.get("smtp_host") - - print(" - NAME: " + str(snake_name)) - print(" - TEMPLATE DIR: " + template_grid_dir) - if compose_src_path: - print(" - COMPOSE SOURCE: " + compose_src_path) - print(" - RELEASE: " + f'{kwargs["node_side_type"]}-{kwargs["release"]}') - print(" - DEPLOYMENT:", kwargs["deployment_type"]) - print(" - ARCH: " + docker_platform) - print(" - TYPE: " + str(node_type.input)) - print(" - DOCKER_TAG: " + version_string) - if version_hash != "dockerhub": - print(" - GIT_HASH: " + version_hash) - print(" - HAGRID_VERSION: " + get_version_string()) - if EDITABLE_MODE: - print(" - HAGRID_REPO_SHA: " + commit_hash()) - print(" - PORT: " + str(host_term.free_port)) - print(" - DOCKER COMPOSE: " + docker_version) - print(" - IN-MEMORY WORKERS: " + str(in_mem_workers)) - print("\n") - - use_blob_storage = ( - False - if str(node_type.input) in ["network", "gateway"] - else bool(kwargs["use_blob_storage"]) - ) - - # use a docker volume - host_path = "credentials-data" - - # # in development use a folder mount - # if kwargs.get("release", "") == "development": - # RELATIVE_PATH = "" - # # if EDITABLE_MODE: - # # RELATIVE_PATH = "../" - # # we might need to change this for the hagrid template mode - # host_path = f"{RELATIVE_PATH}./data/storage/{snake_name}" - - envs = { - "RELEASE": "production", - "COMPOSE_DOCKER_CLI_BUILD": 1, - "DOCKER_BUILDKIT": 1, - "HTTP_PORT": int(host_term.free_port), - "HTTPS_PORT": int(host_term.free_port_tls), - "TRAEFIK_TAG": str(tag), - "NODE_NAME": str(snake_name), - "NODE_TYPE": str(node_type.input), - "TRAEFIK_PUBLIC_NETWORK_IS_EXTERNAL": "False", - "VERSION": version_string, - "VERSION_HASH": version_hash, - "USE_BLOB_STORAGE": str(use_blob_storage), - "FRONTEND_TARGET": "grid-ui-production", - "STACK_API_KEY": str( - generate_sec_random_password(length=48, special_chars=False) - ), - "CREDENTIALS_VOLUME": host_path, - "NODE_SIDE_TYPE": kwargs["node_side_type"], - "SINGLE_CONTAINER_MODE": single_container_mode, - "INMEMORY_WORKERS": in_mem_workers, - } - - if smtp_host and smtp_port and smtp_username and smtp_password: - envs["SMTP_HOST"] = smtp_host - envs["SMTP_PORT"] = smtp_port - envs["SMTP_USERNAME"] = smtp_username - envs["SMTP_PASSWORD"] = smtp_password - envs["EMAIL_SENDER"] = smtp_sender - - if "trace" in kwargs and kwargs["trace"] is True: - envs["TRACE"] = "True" - envs["JAEGER_HOST"] = "host.docker.internal" - envs["JAEGER_PORT"] = int( - find_available_port(host="localhost", port=14268, search=True) - ) - - if "association_request_auto_approval" in kwargs: - envs["ASSOCIATION_REQUEST_AUTO_APPROVAL"] = kwargs[ - "association_request_auto_approval" - ] - - if "enable_warnings" in kwargs: - envs["ENABLE_WARNINGS"] = kwargs["enable_warnings"] - - if "platform" in kwargs and kwargs["platform"] is not None: - envs["DOCKER_DEFAULT_PLATFORM"] = docker_platform - - if "tls" in kwargs and kwargs["tls"] is True and len(kwargs["cert_store_path"]) > 0: - envs["TRAEFIK_TLS_CERTS"] = kwargs["cert_store_path"] - - if ( - "tls" in kwargs - and kwargs["tls"] is True - and "test" in kwargs - and kwargs["test"] is True - ): - envs["IGNORE_TLS_ERRORS"] = "True" - - if "test" in kwargs and kwargs["test"] is True: - envs["SWFS_VOLUME_SIZE_LIMIT_MB"] = "100" # GitHub CI is small - - if kwargs.get("release", "") == "development": - envs["RABBITMQ_MANAGEMENT"] = "-management" - - # currently we only have a domain frontend for dev mode - if kwargs.get("release", "") == "development" and ( - str(node_type.input) not in ["network", "gateway"] - ): - envs["FRONTEND_TARGET"] = "grid-ui-development" - - if "set_root_password" in kwargs and kwargs["set_root_password"] is not None: - envs["DEFAULT_ROOT_PASSWORD"] = kwargs["set_root_password"] - - if "set_root_email" in kwargs and kwargs["set_root_email"] is not None: - envs["DEFAULT_ROOT_EMAIL"] = kwargs["set_root_email"] - - if "set_s3_username" in kwargs and kwargs["set_s3_username"] is not None: - envs["S3_ROOT_USER"] = kwargs["set_s3_username"] - - if "set_s3_password" in kwargs and kwargs["set_s3_password"] is not None: - envs["S3_ROOT_PWD"] = kwargs["set_s3_password"] - - if ( - "set_volume_size_limit_mb" in kwargs - and kwargs["set_volume_size_limit_mb"] is not None - ): - envs["SWFS_VOLUME_SIZE_LIMIT_MB"] = kwargs["set_volume_size_limit_mb"] - - if "release" in kwargs: - envs["RELEASE"] = kwargs["release"] - - if "enable_signup" in kwargs: - envs["ENABLE_SIGNUP"] = kwargs["enable_signup"] - - cmd = "" - args = [] - for k, v in envs.items(): - if is_windows(): - # powershell envs - quoted = f"'{v}'" if not isinstance(v, int) else v - args.append(f"$env:{k}={quoted}") - else: - args.append(f"{k}={v}") - if is_windows(): - cmd += "; ".join(args) - cmd += "; " - else: - cmd += " ".join(args) - - cmd += " docker compose -p " + snake_name - - # new docker compose regression work around - # default_env = os.path.expanduser("~/.hagrid/app/.env") - - default_envs.update(envs) - - # env file path - env_file_path = compose_src_path + "/.env" - - # Render templates if creating stack from the manifest_template.yml - if kwargs["template"] and host_term.host is not None: - # If release is development, update relative path - # if EDITABLE_MODE: - # default_envs["RELATIVE_PATH"] = "../" - - render_templates( - node_name=snake_name, - deployment_type=kwargs["deployment_type"], - template_location=kwargs["template"], - env_vars=default_envs, - host_type=host_term.host, - ) - - try: - env_file = "" - for k, v in default_envs.items(): - env_file += f"{k}={v}\n" - - with open(env_file_path, "w") as f: - f.write(env_file) - - # cmd += f" --env-file {env_file_path}" - except Exception: # nosec - pass - - if single_container_mode: - cmd += " --profile worker" - else: - cmd += " --profile backend" - cmd += " --profile proxy" - cmd += " --profile mongo" - - if str(node_type.input) in ["network", "gateway"]: - cmd += " --profile network" - - if use_blob_storage: - cmd += " --profile blob-storage" - - # no frontend container so expect bad gateway on the / route - if not bool(kwargs["headless"]): - cmd += " --profile frontend" - - if "trace" in kwargs and kwargs["trace"]: - cmd += " --profile telemetry" - - final_commands = {} - final_commands["Pulling"] = pull_command(cmd, kwargs) - - cmd += " --file docker-compose.yml" - if "tls" in kwargs and kwargs["tls"] is True: - cmd += " --file docker-compose.tls.yml" - if "test" in kwargs and kwargs["test"] is True: - cmd += " --file docker-compose.test.yml" - - if build: - my_build_command = build_command(cmd) - final_commands["Building"] = my_build_command - - dev_mode = kwargs.get("dev", False) - final_commands["Launching"] = deploy_command(cmd, tail, dev_mode) - return final_commands - - -def create_launch_vagrant_cmd(verb: GrammarVerb) -> str: - host_term = verb.get_named_term_hostgrammar(name="host") - node_name = verb.get_named_term_type(name="node_name") - node_type = verb.get_named_term_type(name="node_type") - - snake_name = str(node_name.snake_input) - - if ART: - hagrid() - - print( - "Launching a " - + str(node_type.input) - + " PyGrid node on port " - + str(host_term.port) - + "!\n" - ) - - print(" - TYPE: " + str(node_type.input)) - print(" - NAME: " + str(snake_name)) - print(" - PORT: " + str(host_term.port)) - # print(" - VAGRANT: " + "1") - # print(" - VIRTUALBOX: " + "1") - print("\n") - - cmd = "" - cmd += 'ANSIBLE_ARGS="' - cmd += f"-e 'node_name={snake_name}'" - cmd += f"-e 'node_type={node_type.input}'" - cmd += '" ' - cmd += "vagrant up --provision" - cmd = "cd " + GRID_SRC_PATH() + ";" + cmd - return cmd - - -def get_or_make_resource_group(resource_group: str, location: str = "westus") -> None: - cmd = f"az group show --resource-group {resource_group}" - exists = True - try: - subprocess.check_call(cmd, shell=True) # nosec - except Exception: # nosec - # group doesn't exist so lets create it - exists = False - - if not exists: - cmd = f"az group create -l {location} -n {resource_group}" - try: - print(f"Creating resource group.\nRunning: {cmd}") - subprocess.check_call(cmd, shell=True) # nosec - except Exception as e: - raise Exception( - f"Unable to create resource group {resource_group} @ {location}. {e}" - ) - - -def extract_host_ip(stdout: bytes) -> str | None: - output = stdout.decode("utf-8") - - try: - j = json.loads(output) - if "publicIpAddress" in j: - return str(j["publicIpAddress"]) - except Exception: # nosec - matcher = r'publicIpAddress":\s+"(.+)"' - ips = re.findall(matcher, output) - if len(ips) > 0: - return ips[0] - - return None - - -def get_vm_host_ips(node_name: str, resource_group: str) -> list | None: - cmd = f"az vm list-ip-addresses -g {resource_group} --query " - cmd += f""""[?starts_with(virtualMachine.name, '{node_name}')]""" - cmd += '''.virtualMachine.network.publicIpAddresses[0].ipAddress"''' - output = subprocess.check_output(cmd, shell=True) # nosec - try: - host_ips = json.loads(output) - return host_ips - except Exception as e: - print(f"Failed to extract ips: {e}") - - return None - - -def is_valid_ip(host_or_ip: str) -> bool: - matcher = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" - ips = re.findall(matcher, host_or_ip.strip()) - if len(ips) == 1: - return True - return False - - -def extract_host_ip_gcp(stdout: bytes) -> str | None: - output = stdout.decode("utf-8") - - try: - matcher = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" - ips = re.findall(matcher, output) - if len(ips) == 2: - return ips[1] - except Exception: # nosec - pass - - return None - - -def extract_host_ip_from_cmd(cmd: str) -> str | None: - try: - matcher = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" - ips = re.findall(matcher, cmd) - if ips: - return ips[0] - except Exception: # nosec - pass - - return None - - -def check_ip_for_ssh( - host_ip: str, timeout: int = 600, wait_time: int = 5, silent: bool = False -) -> bool: - if not silent: - print(f"Checking VM at {host_ip} is up") - checks = int(timeout / wait_time) # 10 minutes in 5 second chunks - first_run = True - while checks > 0: - checks -= 1 - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(wait_time) - result = sock.connect_ex((host_ip, 22)) - sock.close() - if result == 0: - if not silent: - print(f"VM at {host_ip} is up!") - return True - else: - if first_run: - if not silent: - print("Waiting for VM to start", end="", flush=True) - first_run = False - else: - if not silent: - print(".", end="", flush=True) - except Exception: # nosec - pass - return False - - -def create_aws_security_group( - security_group_name: str, region: str, snake_name: str -) -> str: - sg_description = f"{snake_name} security group" - create_cmd = f"aws ec2 create-security-group --group-name {security_group_name} " - create_cmd += f'--region {region} --description "{sg_description}" ' - sg_output = subprocess.check_output( # nosec - create_cmd, - shell=True, - ) - sg_output_dict = json.loads(sg_output) - if "GroupId" in sg_output_dict: - return sg_output_dict["GroupId"] - - return "" - - -def open_port_aws( - security_group_name: str, port_no: int, cidr: str, region: str -) -> None: - cmd = f"aws ec2 authorize-security-group-ingress --group-name {security_group_name} --protocol tcp " - cmd += f"--port {port_no} --cidr {cidr} --region {region}" - subprocess.check_call( # nosec - cmd, - shell=True, - ) - - -def extract_instance_ids_aws(stdout: bytes) -> list: - output = stdout.decode("utf-8") - output_dict = json.loads(output) - instance_ids: list = [] - if "Instances" in output_dict: - for ec2_instance_metadata in output_dict["Instances"]: - if "InstanceId" in ec2_instance_metadata: - instance_ids.append(ec2_instance_metadata["InstanceId"]) - - return instance_ids - - -def get_host_ips_given_instance_ids( - instance_ids: list, timeout: int = 600, wait_time: int = 10 -) -> list: - checks = int(timeout / wait_time) # 10 minutes in 10 second chunks - instance_ids_str = " ".join(instance_ids) - cmd = f"aws ec2 describe-instances --instance-ids {instance_ids_str}" - cmd += " --query 'Reservations[*].Instances[*].{StateName:State.Name,PublicIpAddress:PublicIpAddress}'" - cmd += " --output json" - while checks > 0: - checks -= 1 - time.sleep(wait_time) - desc_ec2_output = subprocess.check_output(cmd, shell=True) # nosec - instances_output_json = json.loads(desc_ec2_output.decode("utf-8")) - host_ips: list = [] - all_instances_running = True - for reservation in instances_output_json: - for instance_metadata in reservation: - if instance_metadata["StateName"] != "running": - all_instances_running = False - break - else: - host_ips.append(instance_metadata["PublicIpAddress"]) - if all_instances_running: - return host_ips - # else, wait another wait_time seconds and try again - - return [] - - -def make_aws_ec2_instance( - ami_id: str, ec2_instance_type: str, key_name: str, security_group_name: str -) -> list: - # From the docs: "For security groups in a nondefault VPC, you must specify the security group ID". - # Right now, since we're using default VPC, we can use security group name instead of ID. - - ebs_size = 200 # gb - cmd = f"aws ec2 run-instances --image-id {ami_id} --count 1 --instance-type {ec2_instance_type} " - cmd += f"--key-name {key_name} --security-groups {security_group_name} " - tmp_cmd = rf"[{{\"DeviceName\":\"/dev/sdf\",\"Ebs\":{{\"VolumeSize\":{ebs_size},\"DeleteOnTermination\":false}}}}]" - cmd += f'--block-device-mappings "{tmp_cmd}"' - - host_ips: list = [] - try: - print(f"Creating EC2 instance.\nRunning: {cmd}") - create_ec2_output = subprocess.check_output(cmd, shell=True) # nosec - instance_ids = extract_instance_ids_aws(create_ec2_output) - host_ips = get_host_ips_given_instance_ids(instance_ids=instance_ids) - except Exception as e: - print("failed", e) - - if not (host_ips): - raise Exception("Failed to create EC2 instance(s) or get public ip(s)") - - return host_ips - - -def create_launch_aws_cmd( - verb: GrammarVerb, - region: str, - ec2_instance_type: str, - security_group_name: str, - aws_security_group_cidr: str, - key_name: str, - key_path: str, - ansible_extras: str, - kwargs: dict[str, Any], - repo: str, - branch: str, - ami_id: str, - username: str, - auth: AuthCredentials, -) -> list[str]: - node_name = verb.get_named_term_type(name="node_name") - snake_name = str(node_name.snake_input) - create_aws_security_group(security_group_name, region, snake_name) - open_port_aws( - security_group_name=security_group_name, - port_no=80, - cidr=aws_security_group_cidr, - region=region, - ) # HTTP - open_port_aws( - security_group_name=security_group_name, - port_no=443, - cidr=aws_security_group_cidr, - region=region, - ) # HTTPS - open_port_aws( - security_group_name=security_group_name, - port_no=22, - cidr=aws_security_group_cidr, - region=region, - ) # SSH - if kwargs["jupyter"]: - open_port_aws( - security_group_name=security_group_name, - port_no=8888, - cidr=aws_security_group_cidr, - region=region, - ) # Jupyter - - host_ips = make_aws_ec2_instance( - ami_id=ami_id, - ec2_instance_type=ec2_instance_type, - key_name=key_name, - security_group_name=security_group_name, - ) - - launch_cmds: list[str] = [] - - for host_ip in host_ips: - # get old host - host_term = verb.get_named_term_hostgrammar(name="host") - - # replace - host_term.parse_input(host_ip) - verb.set_named_term_type(name="host", new_term=host_term) - - if not bool(kwargs["provision"]): - print("Skipping automatic provisioning.") - print("VM created with:") - print(f"IP: {host_ip}") - print(f"Key: {key_path}") - print("\nConnect with:") - print(f"ssh -i {key_path} {username}@{host_ip}") - - else: - extra_kwargs = { - "repo": repo, - "branch": branch, - "ansible_extras": ansible_extras, - } - kwargs.update(extra_kwargs) - - # provision - host_up = check_ip_for_ssh(host_ip=host_ip) - if not host_up: - print(f"Warning: {host_ip} ssh not available yet") - launch_cmd = create_launch_custom_cmd(verb=verb, auth=auth, kwargs=kwargs) - launch_cmds.append(launch_cmd) - - return launch_cmds - - -def make_vm_azure( - node_name: str, - resource_group: str, - username: str, - password: str | None, - key_path: str | None, - size: str, - image_name: str, - node_count: int, -) -> list: - disk_size_gb = "200" - try: - temp_dir = tempfile.TemporaryDirectory() - public_key_path = ( - private_to_public_key( - private_key_path=key_path, temp_path=temp_dir.name, username=username - ) - if key_path - else None - ) - except Exception: # nosec - temp_dir.cleanup() - - authentication_type = "ssh" if key_path else "password" - cmd = f"az vm create -n {node_name} -g {resource_group} --size {size} " - cmd += f"--image {image_name} --os-disk-size-gb {disk_size_gb} " - cmd += f"--public-ip-sku Standard --authentication-type {authentication_type} --admin-username {username} " - cmd += f"--ssh-key-values {public_key_path} " if public_key_path else "" - cmd += f"--admin-password '{password}' " if password else "" - cmd += f"--count {node_count} " if node_count > 1 else "" - - host_ips: list | None = [] - try: - print(f"Creating vm.\nRunning: {hide_azure_vm_password(cmd)}") - subprocess.check_output(cmd, shell=True) # nosec - host_ips = get_vm_host_ips(node_name=node_name, resource_group=resource_group) - except Exception as e: - print("failed", e) - finally: - temp_dir.cleanup() - - if not host_ips: - raise Exception("Failed to create vm or get VM public ip") - - try: - # clean up temp public key - if public_key_path: - os.unlink(public_key_path) - except Exception: # nosec - pass - - return host_ips - - -def open_port_vm_azure( - resource_group: str, node_name: str, port_name: str, port: int, priority: int -) -> None: - cmd = f"az network nsg rule create --resource-group {resource_group} " - cmd += f"--nsg-name {node_name}NSG --name {port_name} --destination-port-ranges {port} --priority {priority}" - try: - print(f"Creating {port_name} {port} ngs rule.\nRunning: {cmd}") - output = subprocess.check_call(cmd, shell=True) # nosec - print("output", output) - pass - except Exception as e: - print("failed", e) - - -def create_project(project_id: str) -> None: - cmd = f"gcloud projects create {project_id} --set-as-default" - try: - print(f"Creating project.\nRunning: {cmd}") - subprocess.check_call(cmd, shell=True) # nosec - except Exception as e: - print("failed", e) - - print("create project complete") - - -def create_launch_gcp_cmd( - verb: GrammarVerb, - project_id: str, - zone: str, - machine_type: str, - ansible_extras: str, - kwargs: dict[str, Any], - repo: str, - branch: str, - auth: AuthCredentials, -) -> str: - # create project if it doesn't exist - create_project(project_id) - # vm - node_name = verb.get_named_term_type(name="node_name") - kebab_name = str(node_name.kebab_input) - disk_size_gb = "200" - host_ip = make_gcp_vm( - vm_name=kebab_name, - project_id=project_id, - zone=zone, - machine_type=machine_type, - disk_size_gb=disk_size_gb, - ) - - # get old host - host_term = verb.get_named_term_hostgrammar(name="host") - - host_up = check_ip_for_ssh(host_ip=host_ip) - if not host_up: - raise Exception(f"Something went wrong launching the VM at IP: {host_ip}.") - - if not bool(kwargs["provision"]): - print("Skipping automatic provisioning.") - print("VM created with:") - print(f"IP: {host_ip}") - print(f"User: {auth.username}") - print(f"Key: {auth.key_path}") - print("\nConnect with:") - print(f"ssh -i {auth.key_path} {auth.username}@{host_ip}") - sys.exit(0) - - # replace - host_term.parse_input(host_ip) - verb.set_named_term_type(name="host", new_term=host_term) - - extra_kwargs = { - "repo": repo, - "branch": branch, - "auth_type": "key", - "ansible_extras": ansible_extras, - } - kwargs.update(extra_kwargs) - - # provision - return create_launch_custom_cmd(verb=verb, auth=auth, kwargs=kwargs) - - -def make_gcp_vm( - vm_name: str, project_id: str, zone: str, machine_type: str, disk_size_gb: str -) -> str: - create_cmd = "gcloud compute instances create" - network_settings = "network=default,network-tier=PREMIUM" - maintenance_policy = "MIGRATE" - scopes = [ - "https://www.googleapis.com/auth/devstorage.read_only", - "https://www.googleapis.com/auth/logging.write", - "https://www.googleapis.com/auth/monitoring.write", - "https://www.googleapis.com/auth/servicecontrol", - "https://www.googleapis.com/auth/service.management.readonly", - "https://www.googleapis.com/auth/trace.append", - ] - tags = "http-server,https-server" - disk_image = "projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20230429" - disk = ( - f"auto-delete=yes,boot=yes,device-name={vm_name},image={disk_image}," - + f"mode=rw,size={disk_size_gb},type=pd-ssd" - ) - security_flags = ( - "--no-shielded-secure-boot --shielded-vtpm " - + "--shielded-integrity-monitoring --reservation-affinity=any" - ) - - cmd = ( - f"{create_cmd} {vm_name} " - + f"--project={project_id} " - + f"--zone={zone} " - + f"--machine-type={machine_type} " - + f"--create-disk={disk} " - + f"--network-interface={network_settings} " - + f"--maintenance-policy={maintenance_policy} " - + f"--scopes={','.join(scopes)} --tags={tags} " - + f"{security_flags}" - ) - - host_ip = None - try: - print(f"Creating vm.\nRunning: {cmd}") - output = subprocess.check_output(cmd, shell=True) # nosec - host_ip = extract_host_ip_gcp(stdout=output) - except Exception as e: - print("failed", e) - - if host_ip is None: - raise Exception("Failed to create vm or get VM public ip") - - return host_ip - - -def create_launch_azure_cmd( - verb: GrammarVerb, - resource_group: str, - location: str, - size: str, - username: str, - password: str | None, - key_path: str | None, - repo: str, - branch: str, - auth: AuthCredentials, - ansible_extras: str, - kwargs: dict[str, Any], -) -> list[str]: - get_or_make_resource_group(resource_group=resource_group, location=location) - - node_count = kwargs.get("node_count", 1) - print("Total VMs to create: ", node_count) - - # vm - node_name = verb.get_named_term_type(name="node_name") - snake_name = str(node_name.snake_input) - image_name = get_azure_image(kwargs["image_name"]) - host_ips = make_vm_azure( - snake_name, - resource_group, - username, - password, - key_path, - size, - image_name, - node_count, - ) - - # open port 80 - open_port_vm_azure( - resource_group=resource_group, - node_name=snake_name, - port_name="HTTP", - port=80, - priority=500, - ) - - # open port 443 - open_port_vm_azure( - resource_group=resource_group, - node_name=snake_name, - port_name="HTTPS", - port=443, - priority=501, - ) - - if kwargs["jupyter"]: - # open port 8888 - open_port_vm_azure( - resource_group=resource_group, - node_name=snake_name, - port_name="Jupyter", - port=8888, - priority=502, - ) - - launch_cmds: list[str] = [] - - for host_ip in host_ips: - # get old host - host_term = verb.get_named_term_hostgrammar(name="host") - - # replace - host_term.parse_input(host_ip) - verb.set_named_term_type(name="host", new_term=host_term) - - if not bool(kwargs["provision"]): - print("Skipping automatic provisioning.") - print("VM created with:") - print(f"Name: {snake_name}") - print(f"IP: {host_ip}") - print(f"User: {username}") - print(f"Password: {password}") - print(f"Key: {key_path}") - print("\nConnect with:") - if kwargs["auth_type"] == "key": - print(f"ssh -i {key_path} {username}@{host_ip}") - else: - print(f"ssh {username}@{host_ip}") - else: - extra_kwargs = { - "repo": repo, - "branch": branch, - "ansible_extras": ansible_extras, - } - kwargs.update(extra_kwargs) - - # provision - host_up = check_ip_for_ssh(host_ip=host_ip) - if not host_up: - print(f"Warning: {host_ip} ssh not available yet") - launch_cmd = create_launch_custom_cmd(verb=verb, auth=auth, kwargs=kwargs) - launch_cmds.append(launch_cmd) - - return launch_cmds - - -def create_ansible_land_cmd( - verb: GrammarVerb, auth: AuthCredentials | None, kwargs: dict[str, Any] -) -> str: - try: - host_term = verb.get_named_term_hostgrammar(name="host") - print("Landing PyGrid node on port " + str(host_term.port) + "!\n") - - print(" - PORT: " + str(host_term.port)) - print("\n") - - grid_path = GRID_SRC_PATH() - playbook_path = grid_path + "/ansible/site.yml" - ansible_cfg_path = grid_path + "/ansible.cfg" - auth = cast(AuthCredentials, auth) - - if not os.path.exists(playbook_path): - print(f"Can't find playbook site.yml at: {playbook_path}") - cmd = f"ANSIBLE_CONFIG={ansible_cfg_path} ansible-playbook " - if host_term.host == "localhost": - cmd += "--connection=local " - cmd += f"-i {host_term.host}, {playbook_path}" - if host_term.host != "localhost" and kwargs["auth_type"] == "key": - cmd += f" --private-key {auth.key_path} --user {auth.username}" - elif host_term.host != "localhost" and kwargs["auth_type"] == "password": - cmd += f" -c paramiko --user {auth.username}" - - ANSIBLE_ARGS = {"install": "false"} - - if host_term.host != "localhost" and kwargs["auth_type"] == "password": - ANSIBLE_ARGS["ansible_ssh_pass"] = kwargs["password"] - - if host_term.host == "localhost": - ANSIBLE_ARGS["local"] = "true" - - if "ansible_extras" in kwargs and kwargs["ansible_extras"] != "": - options = kwargs["ansible_extras"].split(",") - for option in options: - parts = option.strip().split("=") - if len(parts) == 2: - ANSIBLE_ARGS[parts[0]] = parts[1] - - for k, v in ANSIBLE_ARGS.items(): - cmd += f" -e \"{k}='{v}'\"" - - cmd = "cd " + grid_path + ";" + cmd - return cmd - except Exception as e: - print(f"Failed to construct custom deployment cmd: {cmd}. {e}") - raise e - - -def create_launch_custom_cmd( - verb: GrammarVerb, auth: AuthCredentials | None, kwargs: dict[str, Any] -) -> str: - try: - host_term = verb.get_named_term_hostgrammar(name="host") - node_name = verb.get_named_term_type(name="node_name") - node_type = verb.get_named_term_type(name="node_type") - # source_term = verb.get_named_term_type(name="source") - - snake_name = str(node_name.snake_input) - - if ART: - hagrid() - - print( - "Launching a " - + str(node_type.input) - + " PyGrid node on port " - + str(host_term.port) - + "!\n" - ) - - print(" - TYPE: " + str(node_type.input)) - print(" - NAME: " + str(snake_name)) - print(" - PORT: " + str(host_term.port)) - print("\n") - - grid_path = GRID_SRC_PATH() - playbook_path = grid_path + "/ansible/site.yml" - ansible_cfg_path = grid_path + "/ansible.cfg" - auth = cast(AuthCredentials, auth) - - if not os.path.exists(playbook_path): - print(f"Can't find playbook site.yml at: {playbook_path}") - cmd = f"ANSIBLE_CONFIG={ansible_cfg_path} ansible-playbook " - if host_term.host == "localhost": - cmd += "--connection=local " - cmd += f"-i {host_term.host}, {playbook_path}" - if host_term.host != "localhost" and kwargs["auth_type"] == "key": - cmd += f" --private-key {auth.key_path} --user {auth.username}" - elif host_term.host != "localhost" and kwargs["auth_type"] == "password": - cmd += f" -c paramiko --user {auth.username}" - - version_string = kwargs["tag"] - if version_string is None: - version_string = "local" - - ANSIBLE_ARGS = { - "node_type": node_type.input, - "node_name": snake_name, - "github_repo": kwargs["repo"], - "repo_branch": kwargs["branch"], - "docker_tag": version_string, - } - - if host_term.host != "localhost" and kwargs["auth_type"] == "password": - ANSIBLE_ARGS["ansible_ssh_pass"] = kwargs["password"] - - if host_term.host == "localhost": - ANSIBLE_ARGS["local"] = "true" - - if "node_side_type" in kwargs: - ANSIBLE_ARGS["node_side_type"] = kwargs["node_side_type"] - - if kwargs["tls"] is True: - ANSIBLE_ARGS["tls"] = "true" - - if "release" in kwargs: - ANSIBLE_ARGS["release"] = kwargs["release"] - - if "set_root_email" in kwargs and kwargs["set_root_email"] is not None: - ANSIBLE_ARGS["root_user_email"] = kwargs["set_root_email"] - - if "set_root_password" in kwargs and kwargs["set_root_password"] is not None: - ANSIBLE_ARGS["root_user_password"] = kwargs["set_root_password"] - - if ( - kwargs["tls"] is True - and "cert_store_path" in kwargs - and len(kwargs["cert_store_path"]) > 0 - ): - ANSIBLE_ARGS["cert_store_path"] = kwargs["cert_store_path"] - - if ( - kwargs["tls"] is True - and "upload_tls_key" in kwargs - and len(kwargs["upload_tls_key"]) > 0 - ): - ANSIBLE_ARGS["upload_tls_key"] = kwargs["upload_tls_key"] - - if ( - kwargs["tls"] is True - and "upload_tls_cert" in kwargs - and len(kwargs["upload_tls_cert"]) > 0 - ): - ANSIBLE_ARGS["upload_tls_cert"] = kwargs["upload_tls_cert"] - - if kwargs["jupyter"] is True: - ANSIBLE_ARGS["jupyter"] = "true" - ANSIBLE_ARGS["jupyter_token"] = generate_sec_random_password( - length=48, upper_case=False, special_chars=False - ) - - if "ansible_extras" in kwargs and kwargs["ansible_extras"] != "": - options = kwargs["ansible_extras"].split(",") - for option in options: - parts = option.strip().split("=") - if len(parts) == 2: - ANSIBLE_ARGS[parts[0]] = parts[1] - - # if mode == "deploy": - # ANSIBLE_ARGS["deploy"] = "true" - - for k, v in ANSIBLE_ARGS.items(): - cmd += f" -e \"{k}='{v}'\"" - - cmd = "cd " + grid_path + ";" + cmd - return cmd - except Exception as e: - print(f"Failed to construct custom deployment cmd: {cmd}. {e}") - raise e - - -def create_land_cmd(verb: GrammarVerb, kwargs: dict[str, Any]) -> str: - host_term = verb.get_named_term_hostgrammar(name="host") - host = host_term.host if host_term.host is not None else "" - - if host in ["docker"]: - target = verb.get_named_term_grammar("node_name").input - prune_volumes: bool = kwargs.get("prune_vol", False) - - if target == "all": - # land all syft nodes - if prune_volumes: - land_cmd = "docker rm `docker ps --filter label=orgs.openmined.syft -q` --force " - land_cmd += "&& docker volume rm " - land_cmd += "$(docker volume ls --filter label=orgs.openmined.syft -q)" - return land_cmd - else: - return "docker rm `docker ps --filter label=orgs.openmined.syft -q` --force" - - version = check_docker_version() - if version: - return create_land_docker_cmd(verb=verb, prune_volumes=prune_volumes) - - elif host == "localhost" or is_valid_ip(host): - parsed_kwargs = {} - if DEPENDENCIES["ansible-playbook"]: - if host != "localhost": - parsed_kwargs["username"] = ask( - question=Question( - var_name="username", - question=f"Username for {host} with sudo privledges?", - default=arg_cache["username"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - parsed_kwargs["auth_type"] = ask( - question=Question( - var_name="auth_type", - question="Do you want to login with a key or password", - default=arg_cache["auth_type"], - kind="option", - options=["key", "password"], - cache=True, - ), - kwargs=kwargs, - ) - if parsed_kwargs["auth_type"] == "key": - parsed_kwargs["key_path"] = ask( - question=Question( - var_name="key_path", - question=f"Private key to access {parsed_kwargs['username']}@{host}?", - default=arg_cache["key_path"], - kind="path", - cache=True, - ), - kwargs=kwargs, - ) - elif parsed_kwargs["auth_type"] == "password": - parsed_kwargs["password"] = ask( - question=Question( - var_name="password", - question=f"Password for {parsed_kwargs['username']}@{host}?", - kind="password", - ), - kwargs=kwargs, - ) - - auth = None - if host != "localhost": - if parsed_kwargs["auth_type"] == "key": - auth = AuthCredentials( - username=parsed_kwargs["username"], - key_path=parsed_kwargs["key_path"], - ) - else: - auth = AuthCredentials( - username=parsed_kwargs["username"], - key_path=parsed_kwargs["password"], - ) - if not auth.valid: - raise Exception(f"Login Credentials are not valid. {auth}") - parsed_kwargs["ansible_extras"] = kwargs["ansible_extras"] - return create_ansible_land_cmd(verb=verb, auth=auth, kwargs=parsed_kwargs) - else: - errors = [] - if not DEPENDENCIES["ansible-playbook"]: - errors.append("ansible-playbook") - raise MissingDependency( - f"Launching a Custom VM requires: {' '.join(errors)}" - ) - - host_options = ", ".join(allowed_hosts) - raise MissingDependency( - f"Launch requires a correct host option, try: {host_options}" - ) - - -def create_land_docker_cmd(verb: GrammarVerb, prune_volumes: bool = False) -> str: - """ - Create docker `land` command to remove containers when a node's name is specified - """ - node_name = verb.get_named_term_type(name="node_name") - snake_name = str(node_name.snake_input) - - path = GRID_SRC_PATH() - env_var = ";export $(cat .env | sed 's/#.*//g' | xargs);" - - cmd = "" - cmd += "docker compose" - cmd += ' --file "docker-compose.yml"' - cmd += ' --project-name "' + snake_name + '"' - cmd += " down --remove-orphans" - - if prune_volumes: - cmd += ( - f' && docker volume rm $(docker volume ls --filter name="{snake_name}" -q)' - ) - - cmd += f" && docker rm $(docker ps --filter name={snake_name} -q) --force" - - cmd = "cd " + path + env_var + cmd - return cmd - - -@click.command( - help="Stop a running PyGrid domain/network node.", - context_settings={"show_default": True}, -) -@click.argument("args", type=str, nargs=-1) -@click.option( - "--cmd", - is_flag=True, - help="Print the cmd without running it", -) -@click.option( - "--ansible-extras", - default="", - type=str, -) -@click.option( - "--build-src", - default=DEFAULT_BRANCH, - required=False, - type=str, - help="Git branch to use for launch / build operations", -) -@click.option( - "--silent", - is_flag=True, - help="Suppress extra outputs", -) -@click.option( - "--force", - is_flag=True, - help="Bypass the prompt during hagrid land", -) -@click.option( - "--prune-vol", - is_flag=True, - help="Prune docker volumes after land.", -) -def land(args: tuple[str], **kwargs: Any) -> None: - verb = get_land_verb() - silent = bool(kwargs["silent"]) - force = bool(kwargs["force"]) - try: - grammar = parse_grammar(args=args, verb=verb) - verb.load_grammar(grammar=grammar) - except BadGrammar as e: - print(e) - return - - try: - update_repo(repo=GIT_REPO(), branch=str(kwargs["build_src"])) - except Exception as e: - print(f"Failed to update repo. {e}") - - try: - cmd = create_land_cmd(verb=verb, kwargs=kwargs) - except Exception as e: - print(f"{e}") - return - - target = verb.get_named_term_grammar("node_name").input - - if not force: - _land_domain = ask( - Question( - var_name="_land_domain", - question=f"Are you sure you want to land {target} (y/n)", - kind="yesno", - ), - kwargs={}, - ) - - grid_path = GRID_SRC_PATH() - - if force or _land_domain == "y": - if not bool(kwargs["cmd"]): - if not silent: - print("Running: \n", cmd) - try: - if silent: - process = subprocess.Popen( # nosec - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=grid_path, - shell=True, - ) - process.communicate() - - print(f"HAGrid land {target} complete!") - else: - subprocess.call(cmd, shell=True, cwd=grid_path) # nosec - except Exception as e: - print(f"Failed to run cmd: {cmd}. {e}") - else: - print("Hagrid land aborted.") - - -cli.add_command(launch) -cli.add_command(land) -cli.add_command(clean) - - -@click.command( - help="Show HAGrid debug information", context_settings={"show_default": True} -) -@click.argument("args", type=str, nargs=-1) -def debug(args: tuple[str], **kwargs: Any) -> None: - debug_info = gather_debug() - print("\n\nWhen reporting bugs, please copy everything between the lines.") - print("==================================================================\n") - print(json.dumps(debug_info)) - print("\n=================================================================\n\n") - - -cli.add_command(debug) - - -DEFAULT_HEALTH_CHECKS = ["host", "UI (Ξ²eta)", "api", "ssh", "jupyter"] -HEALTH_CHECK_FUNCTIONS = { - "host": check_host, - "UI (Ξ²eta)": check_login_page, - "api": check_api_metadata, - "ssh": check_ip_for_ssh, - "jupyter": check_jupyter_server, -} - -HEALTH_CHECK_ICONS = { - "host": "πŸ”Œ", - "UI (Ξ²eta)": "πŸ–±", - "api": "βš™οΈ", - "ssh": "πŸ”", - "jupyter": "πŸ“—", -} - -HEALTH_CHECK_URLS = { - "host": "{ip_address}", - "UI (Ξ²eta)": "http://{ip_address}/login", - "api": "http://{ip_address}/api/v2/openapi.json", - "ssh": "hagrid ssh {ip_address}", - "jupyter": "http://{ip_address}:8888", -} - - -def check_host_health(ip_address: str, keys: list[str]) -> dict[str, bool]: - status = {} - for key in keys: - func: Callable = HEALTH_CHECK_FUNCTIONS[key] # type: ignore - status[key] = func(ip_address, silent=True) - return status - - -def icon_status(status: bool) -> str: - return "βœ…" if status else "❌" - - -def get_health_checks(ip_address: str) -> tuple[bool, list[list[str]]]: - keys = list(DEFAULT_HEALTH_CHECKS) - if "localhost" in ip_address: - new_keys = [] - for key in keys: - if key not in ["host", "jupyter", "ssh"]: - new_keys.append(key) - keys = new_keys - - health_status = check_host_health(ip_address=ip_address, keys=keys) - complete_status = all(health_status.values()) - - # find port from ip_address - try: - port = int(ip_address.split(":")[1]) - except Exception: - # default to 80 - port = 80 - - # url to display based on running environment - display_url = gitpod_url(port).split("//")[1] if is_gitpod() else ip_address - - # figure out how to add this back? - # console.print("[bold magenta]Checking host:[/bold magenta]", ip_address, ":mage:") - table_contents = [] - for key, value in health_status.items(): - table_contents.append( - [ - HEALTH_CHECK_ICONS[key], - key, - HEALTH_CHECK_URLS[key].replace("{ip_address}", display_url), - icon_status(value), - ] - ) - - return complete_status, table_contents - - -def create_check_table( - table_contents: list[list[str]], time_left: int = 0 -) -> rich.table.Table: - table = rich.table.Table() - table.add_column("PyGrid", style="magenta") - table.add_column("Info", justify="left", overflow="fold") - time_left_str = "" if time_left == 0 else str(time_left) - table.add_column(time_left_str, justify="left") - for row in table_contents: - table.add_row(row[1], row[2], row[3]) - return table - - -def get_host_name(container_name: str, by_suffix: str) -> str: - # Assumption we always get proxy containers first. - # if users have old docker compose versios. - # the container names are _ instead of - - # canada_proxy_1 instead of canada-proxy-1 - try: - host_name = container_name[0 : container_name.find(by_suffix) - 1] # noqa: E203 - except Exception: - host_name = "" - return host_name - - -def get_docker_status( - ip_address: str, node_name: str | None -) -> tuple[bool, tuple[str, str]]: - url = from_url(ip_address) - port = url[2] - network_container = ( - shell( - "docker ps --format '{{.Names}} {{.Ports}}' | " + f"grep '0.0.0.0:{port}'" - ) - .strip() - .split(" ")[0] - ) - - # Second conditional handle the case when internal port of worker container - # matches with host port of launched Domain/Network Container - if not network_container or (node_name and node_name not in network_container): - # check if it is a worker container and an internal port was passed - worker_containers_output: str = shell( - "docker ps --format '{{.Names}} {{.Ports}}' | " + f"grep '{port}/tcp'" - ).strip() - if not worker_containers_output or not node_name: - return False, ("", "") - - # If there are worker containers with an internal port - # fetch the worker container with the launched worker name - worker_containers = worker_containers_output.split("\n") - for worker_container in worker_containers: - container_name = worker_container.split(" ")[0] - if node_name in container_name: - network_container = container_name - break - else: - # If the worker container is not created yet - return False, ("", "") - - if "proxy" in network_container: - host_name = get_host_name(network_container, by_suffix="proxy") - - backend_containers = shell( - "docker ps --format '{{.Names}}' | grep 'backend' " - ).split() - - _backend_exists = False - for container in backend_containers: - if host_name in container and "stream" not in container: - _backend_exists = True - break - if not _backend_exists: - return False, ("", "") - - node_type = "Domain" - - # TODO: Identify if node_type is Gateway - # for container in headscale_containers: - # if host_name in container: - # node_type = "Gateway" - # break - - return True, (host_name, node_type) - else: - # health check for worker node - host_name = get_host_name(network_container, by_suffix="worker") - return True, (host_name, "Worker") - - -def get_syft_install_status(host_name: str, node_type: str) -> bool: - container_search = "backend" if node_type != "Worker" else "worker" - search_containers = shell( - "docker ps --format '{{.Names}}' | " + f"grep '{container_search}' " - ).split() - - context_container = None - for container in search_containers: - # stream keyword is for our old container stack - if host_name in container and "stream" not in container: - context_container = container - break - - if not context_container: - print(f"❌ {container_search} Docker Stack for: {host_name} not found") - exit(0) - else: - container_log = shell(f"docker logs {context_container}") - if "Application startup complete" not in container_log: - return False - return True - - -@click.command( - help="Check health of an IP address/addresses or a resource group", - context_settings={"show_default": True}, -) -@click.argument("ip_addresses", type=str, nargs=-1) -@click.option( - "--timeout", - default=300, - help="Timeout for hagrid check command", -) -@click.option( - "--verbose", - is_flag=True, - help="Refresh output", -) -def check( - ip_addresses: list[str], verbose: bool = False, timeout: int | str = 300 -) -> None: - check_status(ip_addresses=ip_addresses, silent=not verbose, timeout=timeout) - - -def _check_status( - ip_addresses: str | list[str], - silent: bool = True, - signal: Event | None = None, - node_name: str | None = None, -) -> None: - OK_EMOJI = RichEmoji("white_heavy_check_mark").to_str() - # Check if ip_addresses is str, then convert to list - if ip_addresses and isinstance(ip_addresses, str): - ip_addresses = [ip_addresses] - console = Console() - node_info = None - if len(ip_addresses) == 0: - headers = {"User-Agent": "curl/7.79.1"} - print("Detecting External IP...") - ip_res = requests.get("https://ifconfig.co", headers=headers) # nosec - ip_address = ip_res.text.strip() - ip_addresses = [ip_address] - - if len(ip_addresses) == 1: - ip_address = ip_addresses[0] - status, table_contents = get_health_checks(ip_address=ip_address) - table = create_check_table(table_contents=table_contents) - max_timeout = 600 - if not status: - table = create_check_table( - table_contents=table_contents, time_left=max_timeout - ) - if silent: - with console.status("Gathering Node information") as console_status: - console_status.update( - "[bold orange_red1]Waiting for Container Creation" - ) - docker_status, node_info = get_docker_status(ip_address, node_name) - while not docker_status: - docker_status, node_info = get_docker_status( - ip_address, node_name - ) - time.sleep(1) - if ( - signal and signal.is_set() - ): # Stop execution if timeout is triggered - return - console.print( - f"{OK_EMOJI} {node_info[0]} {node_info[1]} Containers Created" - ) - - console_status.update("[bold orange_red1]Starting Backend") - syft_install_status = get_syft_install_status( - node_info[0], node_info[1] - ) - while not syft_install_status: - syft_install_status = get_syft_install_status( - node_info[0], node_info[1] - ) - time.sleep(1) - # Stop execution if timeout is triggered - if signal and signal.is_set(): - return - console.print(f"{OK_EMOJI} Backend") - console.print(f"{OK_EMOJI} Startup Complete") - - status, table_contents = get_health_checks(ip_address) - table = create_check_table( - table_contents=table_contents, time_left=max_timeout - ) - else: - while not status: - # Stop execution if timeout is triggered - if signal is not None and signal.is_set(): - return - with Live( - table, refresh_per_second=2, screen=True, auto_refresh=False - ) as live: - max_timeout -= 1 - if max_timeout % 5 == 0: - status, table_contents = get_health_checks(ip_address) - table = create_check_table( - table_contents=table_contents, time_left=max_timeout - ) - live.update(table) - if status: - break - time.sleep(1) - - # TODO: Create new health checks table for Worker Container - if (node_info and node_info[1] != "Worker") or not node_info: - console.print(table) - else: - for ip_address in ip_addresses: - _, table_contents = get_health_checks(ip_address) - table = create_check_table(table_contents=table_contents) - console.print(table) - - -def check_status( - ip_addresses: str | list[str], - silent: bool = True, - timeout: int | str = 300, - node_name: str | None = None, -) -> None: - timeout = int(timeout) - # third party - from rich import print - - signal = Event() - - t = Thread( - target=_check_status, - kwargs={ - "ip_addresses": ip_addresses, - "silent": silent, - "signal": signal, - "node_name": node_name, - }, - ) - t.start() - t.join(timeout=timeout) - - if t.is_alive(): - signal.set() - t.join() - - print(f"Hagrid check command timed out after: {timeout} seconds πŸ•›") - print( - "Please try increasing the timeout or kindly check the docker containers for error logs." - ) - print("You can view your container logs using the following tool:") - print("Tool: [link=https://ctop.sh]Ctop[/link]") - print("Video Explanation: https://youtu.be/BJhlCxerQP4 \n") - - -cli.add_command(check) - - -# add Hagrid info to the cli -@click.command(help="Show HAGrid info", context_settings={"show_default": True}) -def version() -> None: - print(f"HAGRID_VERSION: {get_version_string()}") - if EDITABLE_MODE: - print(f"HAGRID_REPO_SHA: {commit_hash()}") - - -cli.add_command(version) - - -def run_quickstart( - url: str | None = None, - syft: str = "latest", - reset: bool = False, - quiet: bool = False, - pre: bool = False, - test: bool = False, - repo: str = DEFAULT_REPO, - branch: str = DEFAULT_BRANCH, - commit: str | None = None, - python: str | None = None, - zip_file: str | None = None, -) -> None: - try: - quickstart_art() - directory = os.path.expanduser("~/.hagrid/quickstart/") - confirm_reset = None - if reset: - if not quiet: - confirm_reset = click.confirm( - "This will create a new quickstart virtualenv and reinstall Syft and " - "Jupyter. Are you sure you want to continue?" - ) - else: - confirm_reset = True - if confirm_reset is False: - return - - if reset and confirm_reset or not os.path.isdir(directory): - quickstart_setup( - directory=directory, - syft_version=syft, - reset=reset, - pre=pre, - python=python, - ) - downloaded_files = [] - if zip_file: - downloaded_files = fetch_notebooks_from_zipfile( - zip_file, - directory=directory, - reset=reset, - ) - elif url: - downloaded_files = fetch_notebooks_for_url( - url=url, - directory=directory, - reset=reset, - repo=repo, - branch=branch, - commit=commit, - ) - else: - file_path = add_intro_notebook(directory=directory, reset=reset) - downloaded_files.append(file_path) - - if len(downloaded_files) == 0: - raise Exception(f"Unable to find files at: {url}") - file_path = sorted(downloaded_files)[0] - - # add virtualenv path - environ = os.environ.copy() - os_bin_path = "Scripts" if is_windows() else "bin" - venv_dir = directory + ".venv" - environ["PATH"] = venv_dir + os.sep + os_bin_path + os.pathsep + environ["PATH"] - jupyter_binary = "jupyter.exe" if is_windows() else "jupyter" - - if is_windows(): - env_activate_cmd = ( - "(Powershell): " - + "cd " - + venv_dir - + "; " - + os_bin_path - + os.sep - + "activate" - ) - else: - env_activate_cmd = ( - "(Linux): source " + venv_dir + os.sep + os_bin_path + "/activate" - ) - - print(f"To activate your virtualenv {env_activate_cmd}") - - try: - allow_browser = " --no-browser" if is_gitpod() else "" - cmd = ( - venv_dir - + os.sep - + os_bin_path - + os.sep - + f"{jupyter_binary} lab{allow_browser} --ip 0.0.0.0 --notebook-dir={directory} {file_path}" - ) - if test: - jupyter_path = venv_dir + os.sep + os_bin_path + os.sep + jupyter_binary - if not os.path.exists(jupyter_path): - print(f"Failed to install Jupyter in path: {jupyter_path}") - sys.exit(1) - print(f"Jupyter exists at: {jupyter_path}. CI Test mode exiting.") - sys.exit(0) - - disable_toolbar_extension = ( - venv_dir - + os.sep - + os_bin_path - + os.sep - + f"{jupyter_binary} labextension disable @jupyterlab/cell-toolbar-extension" - ) - - subprocess.run( # nosec - disable_toolbar_extension.split(" "), cwd=directory, env=environ - ) - - ON_POSIX = "posix" in sys.builtin_module_names - - def enqueue_output(out: Any, queue: Queue) -> None: - for line in iter(out.readline, b""): - queue.put(line) - out.close() - - proc = subprocess.Popen( # nosec - cmd.split(" "), - cwd=directory, - env=environ, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=ON_POSIX, - ) - queue: Queue = Queue() - thread_1 = Thread(target=enqueue_output, args=(proc.stdout, queue)) - thread_2 = Thread(target=enqueue_output, args=(proc.stderr, queue)) - thread_1.daemon = True # thread dies with the program - thread_1.start() - thread_2.daemon = True # thread dies with the program - thread_2.start() - - display_url = None - console = rich.get_console() - - # keepn reading the queue of stdout + stderr - while True: - try: - if not display_url: - # try to read the line and extract a jupyter url: - with console.status( - "Starting Jupyter service" - ) as console_status: - line = queue.get() - display_url = extract_jupyter_url(line.decode("utf-8")) - if display_url: - display_jupyter_url(url_parts=display_url) - console_status.stop() - except KeyboardInterrupt: - proc.kill() # make sure jupyter gets killed - sys.exit(1) - except Exception: # nosec - pass # nosec - except KeyboardInterrupt: - proc.kill() # make sure jupyter gets killed - sys.exit(1) - except Exception as e: - print(f"Error running quickstart: {e}") - raise e - - -@click.command( - help="Launch a Syft + Jupyter Session with a Notebook URL / Path", - context_settings={"show_default": True}, -) -@click.argument("url", type=str, required=False) -@click.option( - "--reset", - is_flag=True, - default=False, - help="Force hagrid quickstart to setup a fresh virtualenv", -) -@click.option( - "--syft", - default="latest", - help="Choose a syft version or just use latest", -) -@click.option( - "--quiet", - is_flag=True, - help="Silence confirmation prompts", -) -@click.option( - "--pre", - is_flag=True, - help="Install pre-release versions of syft", -) -@click.option( - "--python", - default=None, - help="Specify the path to which python to use", -) -@click.option( - "--test", - is_flag=True, - help="CI Test Mode, don't hang on Jupyter", -) -@click.option( - "--repo", - default=DEFAULT_REPO, - help="Choose a repo to fetch the notebook from or just use OpenMined/PySyft", -) -@click.option( - "--branch", - default=DEFAULT_BRANCH, - help="Choose a branch to fetch from or just use dev", -) -@click.option( - "--commit", - help="Choose a specific commit to fetch the notebook from", -) -def quickstart_cli( - url: str | None = None, - syft: str = "latest", - reset: bool = False, - quiet: bool = False, - pre: bool = False, - test: bool = False, - repo: str = DEFAULT_REPO, - branch: str = DEFAULT_BRANCH, - commit: str | None = None, - python: str | None = None, -) -> None: - return run_quickstart( - url=url, - syft=syft, - reset=reset, - quiet=quiet, - pre=pre, - test=test, - repo=repo, - branch=branch, - commit=commit, - python=python, - ) - - -cli.add_command(quickstart_cli, "quickstart") - - -def display_jupyter_url(url_parts: tuple[str, str, int]) -> None: - url = url_parts[0] - if is_gitpod(): - parts = urlparse(url) - query = getattr(parts, "query", "") - url = gitpod_url(port=url_parts[2]) + "?" + query - - console = rich.get_console() - - tick_emoji = RichEmoji("white_heavy_check_mark").to_str() - link_emoji = RichEmoji("link").to_str() - - console.print( - f"[bold white]{tick_emoji} Jupyter Server is running at:\n{link_emoji} [bold blue]{url}\n" - + "[bold white]Use Control-C to stop this server and shut down all kernels.", - new_line_start=True, - ) - - # if is_gitpod(): - # open_browser_with_url(url=url) - - -def open_browser_with_url(url: str) -> None: - webbrowser.open(url) - - -def extract_jupyter_url(line: str) -> tuple[str, str, int] | None: - jupyter_regex = r"^.*(http.*127.*)" - try: - matches = re.match(jupyter_regex, line) - if matches is not None: - url = matches.group(1).strip() - parts = urlparse(url) - host_or_ip_parts = parts.netloc.split(":") - # netloc is host:port - port = 8888 - if len(host_or_ip_parts) > 1: - port = int(host_or_ip_parts[1]) - host_or_ip = host_or_ip_parts[0] - return (url, host_or_ip, port) - except Exception as e: - print("failed to parse jupyter url", e) - return None - - -def quickstart_setup( - directory: str, - syft_version: str, - reset: bool = False, - pre: bool = False, - python: str | None = None, -) -> None: - console = rich.get_console() - OK_EMOJI = RichEmoji("white_heavy_check_mark").to_str() - - try: - with console.status( - "[bold blue]Setting up Quickstart Environment" - ) as console_status: - os.makedirs(directory, exist_ok=True) - virtual_env_dir = os.path.abspath(directory + ".venv/") - if reset and os.path.exists(virtual_env_dir): - shutil.rmtree(virtual_env_dir) - env = VirtualEnvironment(virtual_env_dir, python=python) - console.print( - f"{OK_EMOJI} Created Virtual Environment {RichEmoji('evergreen_tree').to_str()}" - ) - - # upgrade pip - console_status.update("[bold blue]Installing pip") - env.install("pip", options=["-U"]) - console.print(f"{OK_EMOJI} pip") - - # upgrade packaging - console_status.update("[bold blue]Installing packaging") - env.install("packaging", options=["-U"]) - console.print(f"{OK_EMOJI} packaging") - - # Install jupyter lab - console_status.update("[bold blue]Installing Jupyter Lab") - env.install("jupyterlab") - env.install("ipywidgets") - console.print(f"{OK_EMOJI} Jupyter Lab") - - # Install hagrid - if EDITABLE_MODE: - local_hagrid_dir = Path( - os.path.abspath(Path(hagrid_root()) / "../hagrid") - ) - console_status.update( - f"[bold blue]Installing HAGrid in Editable Mode: {str(local_hagrid_dir)}" - ) - env.install("-e " + str(local_hagrid_dir)) - console.print( - f"{OK_EMOJI} HAGrid in Editable Mode: {str(local_hagrid_dir)}" - ) - else: - console_status.update("[bold blue]Installing hagrid") - env.install("hagrid", options=["-U"]) - console.print(f"{OK_EMOJI} HAGrid") - except Exception as e: - print(e) - raise e - - -def add_intro_notebook(directory: str, reset: bool = False) -> str: - filenames = ["00-quickstart.ipynb", "01-install-wizard.ipynb"] - - files = os.listdir(directory) - try: - files.remove(".venv") - except Exception: # nosec - pass - - existing = 0 - for file in files: - if file in filenames: - existing += 1 - - if existing != len(filenames) or reset: - if EDITABLE_MODE: - local_src_dir = Path(os.path.abspath(Path(hagrid_root()) / "../../")) - for filename in filenames: - file_path = os.path.abspath(f"{directory}/{filename}") - shutil.copyfile( - local_src_dir / f"notebooks/quickstart/{filename}", - file_path, - ) - else: - for filename in filenames: - url = ( - "https://raw.githubusercontent.com/OpenMined/PySyft/dev/" - + f"notebooks/quickstart/{filename}" - ) - file_path, _, _ = quickstart_download_notebook( - url=url, directory=directory, reset=reset - ) - if arg_cache["install_wizard_complete"]: - filename = filenames[0] - else: - filename = filenames[1] - return os.path.abspath(f"{directory}/{filename}") - - -@click.command(help="Walk the Path", context_settings={"show_default": True}) -@click.argument("zip_file", type=str, default="padawan.zip", metavar="ZIPFILE") -def dagobah(zip_file: str) -> None: - if not os.path.exists(zip_file): - for text in ( - f"{zip_file} does not exists.", - "Please specify the path to the zip file containing the notebooks.", - "hagrid dagobah [ZIPFILE]", - ): - print(text, file=sys.stderr) - sys.exit(1) - - return run_quickstart(zip_file=zip_file) - - -cli.add_command(dagobah) - - -def ssh_into_remote_machine( - host_ip: str, - username: str, - auth_type: str, - private_key_path: str | None, - cmd: str = "", -) -> None: - """Access or execute command on the remote machine. - - Args: - host_ip (str): ip address of the VM - private_key_path (str): private key of the VM - username (str): username on the VM - cmd (str, optional): Command to execute on the remote machine. Defaults to "". - """ - try: - if auth_type == "key": - subprocess.call( # nosec - ["ssh", "-i", f"{private_key_path}", f"{username}@{host_ip}", cmd] - ) - elif auth_type == "password": - subprocess.call(["ssh", f"{username}@{host_ip}", cmd]) # nosec - except Exception as e: - raise e - - -@click.command( - help="SSH into the IP address or a resource group", - context_settings={"show_default": True}, -) -@click.argument("ip_address", type=str) -@click.option( - "--cmd", - type=str, - required=False, - default="", - help="Optional: command to execute on the remote machine.", -) -def ssh(ip_address: str, cmd: str) -> None: - kwargs: dict = {} - key_path: str | None = None - - if check_ip_for_ssh(ip_address, timeout=10, silent=False): - username = ask( - question=Question( - var_name="azure_username", - question="What is the username for the VM?", - default=arg_cache["azure_username"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - auth_type = ask( - question=Question( - var_name="auth_type", - question="Do you want to login with a key or password", - default=arg_cache["auth_type"], - kind="option", - options=["key", "password"], - cache=True, - ), - kwargs=kwargs, - ) - - if auth_type == "key": - key_path = ask( - question=Question( - var_name="azure_key_path", - question="Absolute path to the private key of the VM?", - default=arg_cache["azure_key_path"], - kind="string", - cache=True, - ), - kwargs=kwargs, - ) - - # SSH into the remote and execute the command - ssh_into_remote_machine( - host_ip=ip_address, - username=username, - auth_type=auth_type, - private_key_path=key_path, - cmd=cmd, - ) - - -cli.add_command(ssh) - - -# Add hagrid logs command to the CLI -@click.command( - help="Get the logs of the HAGrid node", context_settings={"show_default": True} -) -@click.argument("domain_name", type=str) -def logs(domain_name: str) -> None: # nosec - container_ids = ( - subprocess.check_output( # nosec - f"docker ps -qf name=^{domain_name}-*", shell=True - ) - .decode("utf-8") - .split() - ) - Container = namedtuple("Container", "id name logs") - container_names = [] - for container in container_ids: - container_name = ( - subprocess.check_output( # nosec - "docker inspect --format '{{.Name}}' " + container, shell=True - ) - .decode("utf-8") - .strip() - .replace("/", "") - ) - log_command = "docker logs -f " + container_name - container_names.append( - Container(id=container, name=container_name, logs=log_command) - ) - # Generate a table of the containers and their logs with Rich - table = rich.table.Table(title="Container Logs") - table.add_column("Container ID", justify="center", style="cyan", no_wrap=True) - table.add_column("Container Name", justify="right", style="cyan", no_wrap=True) - table.add_column("Log Command", justify="right", style="cyan", no_wrap=True) - for container in container_names: # type: ignore - table.add_row(container.id, container.name, container.logs) # type: ignore - console = rich.console.Console() - console.print(table) - # Print instructions on how to view the logs - console.print( - rich.panel.Panel( - long_string, - title="How to view logs", - border_style="white", - expand=False, - padding=1, - highlight=True, - ) - ) - - -long_string = ( - "β„Ή [bold green]To view the live logs of a container,copy the log command and paste it into your terminal.[/bold green]\n" # noqa: E501 - + "\n" - + "β„Ή [bold green]The logs will be streamed to your terminal until you exit the command.[/bold green]\n" - + "\n" - + "β„Ή [bold green]To exit the logs, press CTRL+C.[/bold green]\n" - + "\n" - + "🚨 The [bold white]backend,backend_stream & celery[/bold white] [bold green]containers are the most important to monitor for debugging.[/bold green]\n" # noqa: E501 - + "\n" - + " [bold white]--------------- Ctop 🦾 -------------------------[/bold white]\n" - + "\n" - + "🧠 To learn about using [bold white]ctop[/bold white] to monitor your containers,visit https://www.youtube.com/watch?v=BJhlCxerQP4n \n" # noqa: E501 - + "\n" - + " [bold white]----------------- How to view this. πŸ™‚ ---------------[/bold white]\n" - + "\n" - + """β„Ή [bold green]To view this panel again, run the command [bold white]hagrid logs {{NODE_NAME}}[/bold white] [/bold green]\n""" # noqa: E501 - + "\n" - + """🚨 NODE_NAME above is the name of your Hagrid deployment,without the curly braces. E.g hagrid logs canada [bold green]\n""" # noqa: E501 - + "\n" - + " [bold green]HAPPY DEBUGGING! πŸ›πŸžπŸ¦—πŸ¦ŸπŸ¦ πŸ¦ πŸ¦ [/bold green]\n " -) - -cli.add_command(logs) diff --git a/packages/hagrid/hagrid/deps.py b/packages/hagrid/hagrid/deps.py deleted file mode 100644 index 2a97353ce7c..00000000000 --- a/packages/hagrid/hagrid/deps.py +++ /dev/null @@ -1,911 +0,0 @@ -"""The purpose of these functions is to check the local dependencies of the person running the CLI -tool and ensure that things are properly configured for the cli's full use (depending on the user's -operating system.) When dependencies are missing the CLI tool should offer helpful hints about what -course of action to take to install missing dependencies, even offering to run appropriate -installation commands where applicable.""" - -# future -from __future__ import annotations - -# stdlib -from collections.abc import Callable -from dataclasses import dataclass -from dataclasses import field -from datetime import datetime -import getpass -import json -import os -import platform -import re -import shutil -import subprocess # nosec -import sys -import traceback -from typing import Any - -# third party -from packaging import version -from packaging.version import Version -import requests -from rich.console import Console - -# relative -from .exceptions import MissingDependency -from .lib import is_gitpod -from .mode import EDITABLE_MODE -from .nb_output import NBOutput -from .version import __version__ - -LATEST_BETA_SYFT = "0.8.7-beta.7" - -DOCKER_ERROR = """ -You are running an old version of docker, possibly on Linux. You need to install v2. -At the time of writing this, if you are on linux you need to run the following: - -DOCKER_COMPOSE_VERSION=v2.21.0 -curl -sSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 \ - -o ~/.docker/cli-plugins/docker-compose -chmod +x ~/.docker/cli-plugins/docker-compose - -ALERT: you may need to run the following command to make sure you can run without sudo. - -echo $USER //(should return your username) -sudo usermod -aG docker $USER - -... now LOG ALL THE WAY OUT!!! - -...and then you should be good to go. You can check your installation by running: - -docker compose version -""" - -SYFT_MINIMUM_PYTHON_VERSION = (3, 10) -SYFT_MINIMUM_PYTHON_VERSION_STRING = "3.10" -SYFT_MAXIMUM_PYTHON_VERSION = (3, 12, 999) -SYFT_MAXIMUM_PYTHON_VERSION_STRING = "3.12" -WHITE = "\033[0;37m" -GREEN = "\033[0;32m" -YELLOW = "\033[0;33m" -BOLD = "\033[1m" -NO_COLOR = "\033[0;0m" -WARNING_MSG = f"\033[0;33mWARNING:{NO_COLOR}" - - -def get_version_string() -> str: - version = str(__version__) - if EDITABLE_MODE: - version += "-dev" - return version - - -@dataclass -class SetupIssue: - issue_name: str - description: str - command: str | None = None - solution: str | None = None - - -@dataclass -class Dependency: - of: str = "" - name: str = "" - display: str = "" - only_os: str = "" - version: Version | None = version.parse("0.0") - valid: bool = False - issues: list[SetupIssue] = field(default_factory=list) - output_in_text: bool = False - - def check(self) -> None: - pass - - -@dataclass -class DependencySyftOS(Dependency): - of: str = "syft" - - def check(self) -> None: - self.display = "βœ… " + ENVIRONMENT["os"] - if is_windows(): - pass - elif is_apple_silicon(): - pass - - -@dataclass -class DependencySyftPython(Dependency): - of: str = "syft" - - def check(self) -> None: - self.version = sys.version_info - if ( - sys.version_info >= SYFT_MINIMUM_PYTHON_VERSION - and sys.version_info <= SYFT_MAXIMUM_PYTHON_VERSION - ): - self.display = "βœ… Python " + ENVIRONMENT["python_version"] - else: - self.issues.append(python_version_unsupported()) - self.display = "❌ " + ENVIRONMENT["python_version"] - - -@dataclass -class DependencyGridGit(Dependency): - of: str = "grid" - - def check(self) -> None: - binary_info = BinaryInfo( - binary="git", version_cmd="git --version" - ).get_binary_info() - if binary_info.path and binary_info.version: - self.display = "βœ… Git " + str(binary_info.version) - else: - self.issues.append(git_install(self.output_in_text)) - self.display = "❌ Git not installed" - - -MINIMUM_DOCKER_VERSION = "20.0.0" - - -@dataclass -class DependencyGridDocker(Dependency): - of: str = "grid" - - def check(self) -> None: - binary_info = BinaryInfo( - binary="docker", version_cmd="docker --version" - ).get_binary_info() - if binary_info.path and binary_info.version > version.parse( - MINIMUM_DOCKER_VERSION - ): - self.display = "βœ… Docker " + str(binary_info.version) - else: - self.issues.append(docker_install()) - self.display = "❌ Docker not installed" - - -MINIMUM_DOCKER_COMPOSE_VERSION = "2.0.0" - - -@dataclass -class DependencyGridDockerCompose(Dependency): - of: str = "grid" - - def check(self) -> None: - binary_info = BinaryInfo( - binary="docker", version_cmd="docker compose version" - ).get_binary_info() - - if ( - binary_info.path - and binary_info.version - and binary_info.version > version.parse(MINIMUM_DOCKER_COMPOSE_VERSION) - ): - self.display = "βœ… Docker Compose " + str(binary_info.version) - else: - self.issues.append(docker_compose_install()) - self.display = "❌ Docker Compose v2 not installed" - - -@dataclass -class DependencyPyPI(Dependency): - of: str = "none" - package_name: str = "" - package_display_name: str = "" - pre: bool = False - install_issue: Callable = lambda: None # noqa: E731 - update_available_issue: Callable = lambda: None # noqa: E731 - - def check(self) -> None: - package_dict = get_pip_package(self.package_name) - - if package_dict is None: - self.display = "❌ " + f"{self.package_display_name} not installed" - self.issues.append(self.install_issue(pre=self.pre)) - else: - version_string = package_dict["version"] - current_version = version.parse(version_string) - if "editable_project_location" in package_dict: - self.display = ( - "🚨 " - + f"{self.package_name}=={str(current_version)} -e {package_dict['editable_project_location']}" - ) - else: - is_newer, latest_version = new_pypi_version( - package=self.package_name, current=current_version, pre=self.pre - ) - if not is_newer: - channel = "stable" - if current_version.is_prerelease: - channel = "pre-release" - self.display = ( - "βœ… " - + f"{self.package_name}=={str(version_string)} (latest {channel})" - ) - else: - self.display = ( - "βœ… " - + f"{self.package_name}=={str(current_version)} (Version {str(latest_version)} available)" - ) - self.issues.append( - self.update_available_issue(current_version, latest_version) - ) - - -def new_pypi_version( - package: str, current: Version, pre: bool = False -) -> tuple[bool, Version]: - pypi_json = get_pypi_versions(package_name=package) - if ( - "info" not in pypi_json - or "releases" not in pypi_json - or "version" not in pypi_json["info"] - ): - raise Exception("Bad response from PyPi") - - if not current.is_prerelease and not pre: - latest_stable = version.parse(pypi_json["info"]["version"]) - if current < latest_stable: - return (True, latest_stable) - else: - return (False, current) - else: - latest_release = current - - releases = sorted(pypi_json["releases"].keys()) - for release in releases: - pre_release_version = version.parse(release) - if latest_release < pre_release_version: - latest_release = pre_release_version - - if latest_release != current: - return (True, latest_release) - else: - return (False, latest_release) - - -def get_pypi_versions(package_name: str) -> dict[str, Any]: - try: - pypi_url = f"https://pypi.org/pypi/{package_name}/json" - req = requests.get(pypi_url) # nosec - # TODO: Fix JSON parsing of version keys - # this is broken on my machine for some reason, the version keys are wrong - pypi_info = json.loads(req.text) - # print(pypi_info["releases"].keys()) - return pypi_info - - except Exception as e: - print(f"Unable to get JSON from PyPI URL: {pypi_url}. {e}") - raise e - - -def get_pip_package(package_name: str) -> dict[str, str] | None: - packages = get_pip_packages() - for package in packages: - if package["name"] == package_name: - return package - return None - - -def get_pip_packages() -> list[dict[str, str]]: - try: - cmd = "python -m pip list --format=json --disable-pip-version-check" - output = subprocess.check_output(cmd, shell=True) # nosec - return json.loads(str(output.decode("utf-8")).strip()) - except Exception as e: - print("failed to pip list", e) - raise e - - -def get_location(binary: str) -> str | None: - return shutil.which(binary) - - -@dataclass -class BinaryInfo: - binary: str - version_cmd: str - error: str | None = None - path: str | None = None - version: str | Version | None = version.parse("0.0") - version_regex = ( - r"[^\d]*(" - + r"(0|[1-9][0-9]*)\.*(0|[1-9][0-9]*)\.*(0|[1-9][0-9]*)" - + r"(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)" - + r"(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?" - + r"(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?)" - + r"[^\d].*" - ) - - def extract_version(self, lines: list[str]) -> None: - for line in lines: - matches = re.match(self.version_regex, line) - if matches is not None: - self.version = matches.group(1) - try: - if "-gitpod" in self.version: - parts = self.version.split("-gitpod") - self.version = parts[0] - if "-desktop" in self.version: - parts = self.version.split("-desktop") - self.version = parts[0] - self.version = version.parse(self.version) - except Exception: # nosec - pass - break - - def get_binary_info(self) -> BinaryInfo: - self.path = get_location(self.binary) - if self.path: - returncode, lines = get_cli_output(self.version_cmd) - if returncode == 0: - self.extract_version(lines=lines) - else: - if len(lines) > 0: - self.error = lines[0] - else: - self.error = f"Error, no output from {self.binary}" - return self - - -def get_cli_output(cmd: str, timeout: float | None = None) -> tuple[int, list[str]]: - try: - proc = subprocess.Popen( # nosec - cmd.split(" "), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - lines = [] - if proc.stdout and hasattr(proc.stdout, "readlines"): - lines = [line.decode("utf-8") for line in proc.stdout.readlines()] - - if proc.stderr and hasattr(proc.stderr, "readlines"): - lines.extend([line.decode("utf-8") for line in proc.stderr.readlines()]) - - proc.communicate(timeout=timeout) - return (int(proc.returncode), lines) - except Exception as e: - return (-1, [str(e)]) - - -def gather_debug() -> dict[str, Any]: - # relative - from .lib import commit_hash - from .lib import hagrid_root - - now = datetime.now().astimezone() - dt_string = now.strftime("%d/%m/%Y %H:%M:%S %Z") - debug_info: dict[str, Any] = {} - debug_info["datetime"] = dt_string - debug_info["python_binary"] = sys.executable - debug_info["dependencies"] = DEPENDENCIES - debug_info["environment"] = ENVIRONMENT - debug_info["hagrid"] = get_version_string() - debug_info["hagrid_dev"] = EDITABLE_MODE - debug_info["hagrid_path"] = hagrid_root() - debug_info["hagrid_repo_sha"] = commit_hash() - debug_info["docker"] = docker_info() - if is_windows(): - debug_info["wsl"] = wsl_info() - debug_info["wsl_linux"] = wsl_linux_info() - return debug_info - - -def get_environment() -> dict[str, Any]: - return { - "uname": platform.uname(), - "platform": platform.system().lower(), - "os_version": platform.release(), - "python_version": platform.python_version(), - } - - -ENVIRONMENT = get_environment() - - -def os_name() -> str: - os_name = platform.system() - if os_name.lower() == "darwin": - return "macOS" - else: - return os_name - - -ENVIRONMENT["os"] = os_name() - - -def is_apple_silicon() -> bool: - if ( - "platform" in ENVIRONMENT - and ENVIRONMENT["platform"].lower() == "darwin" - and ENVIRONMENT["uname"].machine != "x86_64" - ): - return True - return False - - -ENVIRONMENT["apple_silicon"] = is_apple_silicon() - - -def is_windows() -> bool: - if "platform" in ENVIRONMENT and ENVIRONMENT["platform"].lower() == "windows": - return True - return False - - -allowed_hosts = ["docker", "azure", "aws", "gcp"] -commands = ["docker", "git", "ansible-playbook"] - -if is_windows(): - commands.append("wsl") - - -def check_deps_old() -> dict[str, str | None]: - paths = {} - for dep in commands: - paths[dep] = shutil.which(dep) - return paths - - -DEPENDENCIES = check_deps_old() - - -def docker_info() -> str: - try: - cmd = "docker info" - output = subprocess.check_output(cmd, shell=True) # nosec - return str(output.decode("utf-8")) - except Exception as e: - print("failed to get docker info", e) - return str(e) - - -def wsl_info() -> str: - try: - cmd = "wsl --status" - output = subprocess.check_output(cmd, shell=True) # nosec - return str(output.decode("utf-8")) - except Exception as e: - print("failed to get wsl info", e) - return str(e) - - -def wsl_linux_info() -> str: - try: - cmd = "wsl bash -c 'lsb_release -a'" - output = subprocess.check_output(cmd, shell=True) # nosec - return str(output.decode("utf-8")) - except Exception as e: - print("failed to get wsl linux info", e) - return str(e) - - -def check_docker_version() -> str | None: - if is_windows(): - return "N/A" # todo fix to work with windows - result = os.popen("docker compose version", "r").read() # nosec - version = None - if "version" in result: - version = result.split()[-1] - else: - print("This may be a linux machine, either that or docker compose isn't s") - print("Result:" + result) - out = subprocess.run( # nosec - ["docker", "compose"], capture_output=True, text=True - ) - if "'compose' is not a docker command" in out.stderr: - raise MissingDependency(DOCKER_ERROR) - - return version - - -def docker_running(timeout: float | None = None) -> tuple[bool, str]: - status, error_msg = False, "" - - try: - cmd = "docker info" - returncode, msg = get_cli_output(cmd, timeout=timeout) - if returncode == 0: - status, error_msg = True, "βœ… Docker service is running" - else: - error_msg = f"""❌ Docker service is either not installed or running.\n\n -To install docker, execute the following steps:\n -1 - Install docker on your machine by using the proper steps according to your OS.\n -{WHITE}MacOS: {GREEN}brew install --cask docker -{WHITE}Linux: {GREEN}curl -fsSL https://get.docker.com -o get-docker.sh && chmod +777 get-docker.sh && ./get-docker.sh -{WHITE}Windows: {GREEN}choco install docker-desktop -y{NO_COLOR} \n -2 - Run \'{GREEN}sudo usermod -a -G docker $USER\'{WHITE} to enable this user to execute docker. -3 - log out and log back in so that your group membership is re-evaluated {NO_COLOR}. --------------------------------------------------------------------------------------------------------\n -To start your docker service:\n -1 - {WHITE}MacOS/Windows: One can start docker by clicking on the "Docker" icon in your Applications folder.{NO_COLOR} -2 - {WHITE}Ubuntu: {GREEN}sudo service docker start {NO_COLOR} --------------------------------------------------------------------------------------------------------\n -""" - error_msg += f"""{YELLOW}{BOLD}Std Output Logs{NO_COLOR} -=================\n\n""" + "\n".join(msg) - - except Exception as e: # nosec - error_msg = str(e) - - return status, error_msg - - -def allowed_to_run_docker() -> tuple[bool, str]: - bool_result, msg = True, "" - if platform.system().lower() == "linux": - _, line = get_cli_output("getent group docker") - - # get user - user = getpass.getuser() - - # Check if current user is root. - if os.geteuid() == 0: - bool_result = True - - # Check if current user is member of docker group. - elif not is_gitpod() and user not in "".join(line): - msg = f"""⚠️ User is not a member of docker group. -{WHITE}You're currently not allowed to run docker, perform the following steps:\n - 1 - Run \'{GREEN}sudo usermod -a -G docker $USER\'{WHITE} to add docker permissions. - 2 - log out and log back in so that your group membership is re-evaluated {NO_COLOR}.""" - # NOTE: For some reason, inside of CI pipeline the user (runner) isn't a member of - # docker group and doesn't have sudo priviledges, but can execute docker without - # permission issues. This is just a workaround to avoid raising an exeception - # in this scenario without reason. - if user == "runner": - bool_result = True - else: - bool_result = False - - return bool_result, msg - - -def check_docker_service_status(animated: bool = True) -> None: - """Check the status of the docker service. - - Raises: - MissingDependency: If docker service is not running. - """ - - if not animated: - docker_installed, msg = docker_running(timeout=60) - user_allowed, permission_msg = allowed_to_run_docker() - else: - console = Console() - # putting \t at the end seems to prevent weird chars getting outputted - # during animations in the juypter notebook - with console.status("[bold blue]Checking for Docker Service[/bold blue]\t"): - docker_installed, msg = docker_running(timeout=60) - user_allowed, permission_msg = allowed_to_run_docker() - - # Check if user is allowed to execute docker - if not user_allowed: - raise MissingDependency(permission_msg) - - # If docker bin was not found. - if not docker_installed: - raise MissingDependency(msg) - - print("βœ… Docker service is running") - - -def check_deps( - deps: dict[str, Dependency], - of: str = "", - display: bool = True, - output_in_text: bool = False, -) -> dict[str, Dependency] | NBOutput: - output = "" - if len(of) > 0: - of = f" {of}" - # output += f"Checking{of} Dependencies:\n" - issues = [] - for dep in deps.values(): - dep.check() - output += (dep.display + "\n") if display else "" - issues += dep.issues - - if not output_in_text: - if len(issues) > 0: - output += "

🚨 Some issues were found

" - for issue in issues: - output += f"
Issue: {issue.description}
" - if issue.solution != "": - output += f"Solution:\n{issue.solution}" - if issue.command != "": - output += ( - "
Command:\n " - + f"[ ]!{issue.command}
" - ) - output += "\n" - - return NBOutput(output).to_html() - else: - if len(issues) > 0: - output += "🚨 Some issues were found\n" - for issue in issues: - output += f"\nIssue: {issue.description}\n" - if issue.solution != "": - output += f"\nSolution:\n{issue.solution}\n" - if issue.command != "": - output += "\nCommand:\n" + f"{issue.command} " - output += "\n" - - if len(output) > 0: - print(output) - return None # type: ignore - - -def check_grid_docker( - display: bool = True, output_in_text: bool = False -) -> dict[str, Dependency] | NBOutput: - try: - deps: dict[str, Dependency] = {} - deps["git"] = DependencyGridGit(name="git") - deps["docker"] = DependencyGridDocker(name="docker") - deps["docker_compose"] = DependencyGridDockerCompose(name="docker compose") - return check_deps( - of="Grid", deps=deps, display=display, output_in_text=output_in_text - ) - except Exception as e: - try: - if display and not output_in_text: - return NBOutput(debug_exception(e=e)).to_html() - except Exception: # nosec - pass - - print(e) - raise e - - -def debug_exception(e: Exception) -> str: - exception = ( - f'
An exception occured: {e}.
' - + "Please file a bug report on GitHub Issues or in Slack #support
" - ) - exception += "\n" - exception += ".\n" - exception += "https://slack.openmined.org/\n" - exception += "https://github.com/OpenMined/PySyft/issues\n" - exception += "\n\nWhen reporting bugs, please copy everything between the lines.\n" - exception += "==================================================================\n" - exception += ( - "" + json.dumps(gather_debug(), indent=4, sort_keys=True) + "" - ) - exception += "\n" - exception += traceback.format_exc() - exception += ( - "\n=================================================================\n\n" - ) - return exception - - -def check_syft_deps(display: bool = True) -> dict[str, Dependency] | NBOutput: - try: - deps: dict[str, Dependency] = {} - deps["os"] = DependencySyftOS(name="os") - deps["python"] = DependencySyftPython(name="python") - return check_deps(of="Syft", deps=deps, display=display) - except Exception as e: - try: - if display: - return NBOutput(debug_exception(e=e)).to_html() - except Exception: # nosec - pass - - print(e) - raise e - - -def check_hagrid(display: bool = True) -> dict[str, Dependency] | NBOutput: - try: - deps: dict[str, Dependency] = {} - deps["hagrid"] = DependencyPyPI( - package_name="hagrid", - package_display_name="HAGrid", - update_available_issue=hagrid_update_available, - ) - return check_deps(deps=deps, display=display) - except Exception as e: - try: - if display: - return NBOutput(debug_exception(e=e)).to_html() - except Exception: # nosec - pass - - print(e) - raise e - - -def check_syft( - display: bool = True, pre: bool = False -) -> dict[str, Dependency] | NBOutput: - try: - deps: dict[str, Dependency] = {} - deps["os"] = DependencySyftOS(name="os") - deps["python"] = DependencySyftPython(name="python") - deps["syft"] = DependencyPyPI( - package_name="syft", - package_display_name="Syft", - pre=pre, - install_issue=syft_install, - update_available_issue=syft_update_available, - ) - return check_deps(deps=deps, display=display) - except Exception as e: - try: - if display: - return NBOutput(debug_exception(e=e)).to_html() - except Exception: # nosec - pass - - print(e) - raise e - - -PACKAGE_MANAGER_COMMANDS = { - "git": { - "macos": "brew install git", - "windows": 'choco install git.install --params "/GitAndUnixToolsOnPath /WindowsTerminal /NoAutoCrlf" -y', - "linux": "sudo apt update && sudo apt install git", - "backup_url": "https://git-scm.com/downloads", - }, - "docker": { - "macos": "brew install --cask docker", - "windows": "choco install docker-desktop -y", - "linux": "curl -fsSL https://get.docker.com -o get-docker.sh && chmod +777 get-docker.sh && ./get-docker.sh", - "backup_url": "https://www.docker.com/products/docker-desktop/", - }, - "docker_compose": { - "macos": "brew install --cask docker", - "windows": "choco install docker-desktop -y", - "linux": ( - "mkdir -p ~/.docker/cli-plugins\n" - + "DOCKER_COMPOSE_VERSION=v2.21.0\n" - + "curl -sSL https://github.com/docker/compose/releases/download/" - + "${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 " - + "-o ~/.docker/cli-plugins/docker-compose\n" - + "chmod +x ~/.docker/cli-plugins/docker-compose" - ), - "backup_url": "https://github.com/docker/compose", - }, -} - -PACKAGE_MANAGERS = { - "macos": "brew", - "windows": "choco", - "linux": "apt", -} - - -def os_package_manager_install_cmd( - package_name: str, package_display_name: str, output_in_text: bool = False -) -> tuple[str | None, str | None]: - os = ENVIRONMENT["os"].lower() - cmd = None - url = None - package_manager = PACKAGE_MANAGERS[os] - if ( - package_name in PACKAGE_MANAGER_COMMANDS - and os in PACKAGE_MANAGER_COMMANDS[package_name] - ): - cmd = PACKAGE_MANAGER_COMMANDS[package_name][os] - if ( - package_name in PACKAGE_MANAGER_COMMANDS - and "backup_url" in PACKAGE_MANAGER_COMMANDS[package_name] - ): - url = PACKAGE_MANAGER_COMMANDS[package_name]["backup_url"] - - solution = "" - - if not output_in_text: - if cmd: - solution += f"- You can install {package_display_name} with {package_manager}\n" - if url: - if cmd: - solution += "- Alternatively, you " - else: - solution += "- You " - solution += f"can download and install {package_display_name}" - solution += f'from
{url}' - else: - if cmd: - solution += ( - f"- You can install {package_display_name} with {package_manager}\n" - ) - if url: - if cmd: - solution += "- Alternatively, you " - else: - solution += "- You " - solution += f"can download and install {package_display_name} from {url}" - - return (cmd, solution) - - -def docker_compose_install() -> SetupIssue: - command, solution = os_package_manager_install_cmd( - package_name="docker_compose", package_display_name="Docker Compose" - ) - return SetupIssue( - issue_name="docker_compose_install", - description="You do not have Docker Compose v2 installed.", - command=command, - solution=solution, - ) - - -def docker_install() -> SetupIssue: - command, solution = os_package_manager_install_cmd( - package_name="docker", package_display_name="Docker" - ) - return SetupIssue( - issue_name="docker_install", - description="You do not have Docker installed.", - command=command, - solution=solution, - ) - - -def git_install(output_in_text: bool = False) -> SetupIssue: - command, solution = os_package_manager_install_cmd( - package_name="git", package_display_name="Git", output_in_text=output_in_text - ) - return SetupIssue( - issue_name="git_install", - description="You do not have Git installed.", - command=command, - solution=solution, - ) - - -def syft_install(pre: bool = False) -> SetupIssue: - command = "pip install -U syft" - if pre: - # command += " --pre" - pass - return SetupIssue( - issue_name="syft_install", - description="You have not installed Syft.", - command=command, - solution="You can install Syft with pip.", - ) - - -def syft_update_available(current_version: Version, new_version: Version) -> SetupIssue: - return SetupIssue( - issue_name="syft_update_available", - description=( - "A new release of Syft is available: " - + f"{str(current_version)} -> {str(new_version)}." - ), - command=f"pip install syft=={new_version}", - solution="You can upgrade Syft with pip.", - ) - - -def hagrid_update_available( - current_version: Version, new_version: Version -) -> SetupIssue: - return SetupIssue( - issue_name="hagrid_update_available", - description=( - "A new release of HAGrid is available: " - + f"{str(current_version)} -> {str(new_version)}." - ), - command=f"pip install -U hagrid=={new_version}", - solution="You can upgrade HAGrid with pip.", - ) - - -def python_version_unsupported() -> SetupIssue: - return SetupIssue( - issue_name="python_version_unsupported", - description=( - f"Syft supports Python >= {SYFT_MINIMUM_PYTHON_VERSION_STRING} " - + f"and <= {SYFT_MAXIMUM_PYTHON_VERSION_STRING}" - ), - command="", - solution="You must install a compatible version of Python", - ) diff --git a/packages/hagrid/hagrid/dummynum.py b/packages/hagrid/hagrid/dummynum.py deleted file mode 100644 index 06b28f28682..00000000000 --- a/packages/hagrid/hagrid/dummynum.py +++ /dev/null @@ -1,28 +0,0 @@ -# stdlib -from typing import Any - -# a dummy enum - - -class Meta(type): - # any property returns another dummy which can also be executed - def __getattribute__(cls, name: str) -> Any: - try: - return super().__getattribute__(name) - except Exception: # nosec - pass - return return_dummy() - - -# this lets us prevent runtime errors of missing types in older syft -class DummyNum(metaclass=Meta): - def __init__(self, *args: Any, **kwargs: Any) -> None: - pass - - def __call__(self, *args: Any, **kwargs: Any) -> Any: - return self - - -def return_dummy() -> DummyNum: - # this lets us create the sub class in the parent meta on getattr - return DummyNum() diff --git a/packages/hagrid/hagrid/exceptions.py b/packages/hagrid/hagrid/exceptions.py deleted file mode 100644 index 0bb68a0dd55..00000000000 --- a/packages/hagrid/hagrid/exceptions.py +++ /dev/null @@ -1,2 +0,0 @@ -class MissingDependency(Exception): - pass diff --git a/packages/hagrid/hagrid/file.py b/packages/hagrid/hagrid/file.py deleted file mode 100644 index 378f5fdcf10..00000000000 --- a/packages/hagrid/hagrid/file.py +++ /dev/null @@ -1,8 +0,0 @@ -# stdlib -import os - - -def user_hagrid_profile() -> str: - dir_path = os.path.expanduser("~/.hagrid") - os.makedirs(dir_path, exist_ok=True) - return os.path.abspath(dir_path) diff --git a/packages/hagrid/hagrid/git_check.py b/packages/hagrid/hagrid/git_check.py deleted file mode 100644 index c98028f0c52..00000000000 --- a/packages/hagrid/hagrid/git_check.py +++ /dev/null @@ -1,15 +0,0 @@ -# relative -from .deps import DependencyGridGit -from .deps import check_deps - - -def verify_git_installation() -> None: - dep = DependencyGridGit(name="git", output_in_text=True) - deps = {} - deps["git"] = dep - check_deps(of="Git", deps=deps, display=False, output_in_text=True) # type: ignore - if dep.issues: - exit(1) - - -verify_git_installation() diff --git a/packages/hagrid/hagrid/grammar.py b/packages/hagrid/hagrid/grammar.py deleted file mode 100644 index 62f98d47fe8..00000000000 --- a/packages/hagrid/hagrid/grammar.py +++ /dev/null @@ -1,365 +0,0 @@ -# future -from __future__ import annotations - -# stdlib -from collections.abc import Callable -import socket -from typing import Any - -# relative -from .deps import allowed_hosts -from .lib import find_available_port - -ALLOWED_NODE_TYPES = ["domain", "network", "gateway", "enclave"] - - -class BadGrammar(Exception): - pass - - -class GrammarVerb: - def __init__( - self, - command: str, - full_sentence: list[dict[str, Any]], - abbreviations: dict[int, list[str | None]], - ) -> None: - self.grammar: list[GrammarTerm | HostGrammarTerm | SourceGrammarTerm] = [] - self.command = command - self.full_sentence = full_sentence - self.abbreviations = abbreviations - - def get_named_term_grammar(self, name: str) -> GrammarTerm: - for term in self.grammar: - if term.name == name and isinstance(term, GrammarTerm): - return term - raise BadGrammar(f"GrammarTerm with {name} not found in {self.grammar}") - - def get_named_term_hostgrammar(self, name: str) -> HostGrammarTerm: - for term in self.grammar: - if term.name == name and isinstance(term, HostGrammarTerm): - return term - raise BadGrammar(f"HostGrammarTerm with {name} not found in {self.grammar}") - - def get_named_term_type( - self, name: str, term_type: str | None = None - ) -> GrammarTerm | HostGrammarTerm: - if term_type == "host": - return self.get_named_term_hostgrammar(name=name) - return self.get_named_term_grammar(name=name) - - def set_named_term_type( - self, name: str, new_term: GrammarTerm, term_type: str | None = None - ) -> None: - new_grammar = [] - for term in self.grammar: - found = False - if term.name == name: - if term_type is not None and term.type == term_type: - found = True - elif term_type is None: - found = True - if not found: - new_grammar.append(term) - else: - new_grammar.append(new_term) - self.grammar = new_grammar - - def load_grammar( - self, grammar: list[GrammarTerm | HostGrammarTerm | SourceGrammarTerm] - ) -> None: - self.grammar = grammar - - -class GrammarTerm: - def __init__( - self, - type: str, - name: str, - default: str | Callable | None = None, - options: list | None = None, - example: str | None = None, - **kwargs: Any, - ) -> None: - self.raw_input: str | None = None - self.input: str | None = None - self.type = type - self.name = name - self.default = default - self.options = options if options is not None else [] - self.example = example - - @property - def snake_input(self) -> str | None: - if self.input: - return self.input.lower().replace(" ", "_") - return None - - @property - def kebab_input(self) -> str | None: - if self.input: - return self.input.lower().replace(" ", "-") - return None - - def __repr__(self) -> str: - return f"<{type(self).__name__}: {self.name}<{self.type}>: {self.input} [raw: {self.raw_input}]>" - - def get_example(self) -> str: - return_value = self.example if self.example else self.default - if callable(return_value): - return_value = return_value() - return str(return_value) - - # no op - def custom_parsing(self, input: str) -> str: - return input - - def parse_input(self, input: str | None) -> None: - self.raw_input = input - if input is None and self.default is None: - raise BadGrammar( - f"{self.name} has no default, please use one of the following options: {self.options}" - ) - if input is None: - if isinstance(self.default, str): - input = self.default - elif callable(self.default): - input = self.default() - - if len(self.options) > 0 and input not in self.options: - raise BadGrammar( - f"{input} is not valid for {self.name} please use one of the following options: {self.options}" - ) - - self.input = self.custom_parsing(input=input) if input else input - - -class HostGrammarTerm(GrammarTerm): - @property - def host(self) -> str | None: - return self.parts()[0] - - @property - def port(self) -> int | None: - return self.parts()[1] - - @property - def search(self) -> bool: - return bool(self.parts()[2]) - - @property - def port_tls(self) -> int: - if self.port == 80: - return 443 - return 444 - - @property - def free_port(self) -> int: - if self.port is None: - raise BadGrammar( - f"{type(self)} unable to check if port {self.port} is free" - ) - return find_available_port(host="localhost", port=self.port, search=self.search) - - @property - def free_port_tls(self) -> int: - if self.port_tls is None: - raise BadGrammar( - f"{type(self)} unable to check if tls port {self.port_tls} is free" - ) - return find_available_port(host="localhost", port=self.port_tls, search=True) - - def parts(self) -> tuple[str | None, int | None, bool]: - host = None - port: int | None = None - search = False - if self.input: - parts = self.input.split(":") - host = parts[0] - if len(parts) > 1: - port_str = parts[1] - if port_str.endswith("+"): - search = True - port_str = port_str[0:-1] - port = int(port_str) - return (host, port, search) - - def validate_host(self, host_or_ip: str) -> bool: - try: - if socket.gethostbyname(host_or_ip) == host_or_ip: - return True - elif socket.gethostbyname(host_or_ip) != host_or_ip: - return True - except socket.gaierror: - raise BadGrammar( - f"{host_or_ip} is not valid for {self.name}. Try an IP, hostname or docker, vm, aws, azure or gcp" - ) - return False - - def validate_port(self, port: str) -> bool: - try: - if port.endswith("+"): - int(port[0:-1]) - else: - int(port) - except Exception: # nosec - raise BadGrammar( - f"{port} is not a valid port option. Try: {self.get_example()}" - ) - return True - - def custom_parsing(self, input: str) -> str: - colons = input.count(":") - host = input - port = None - if colons > 1: - raise BadGrammar( - f"You cannot have more than one : for {self.name}, try: {self.get_example()}" - ) - elif colons == 1: - parts = input.split(":") - host = parts[0] - port = parts[1] - - if port is None: - if host == "docker": - port = "8081+" # default - else: - port = "80" # default - - if host not in allowed_hosts: - _ = self.validate_host(host_or_ip=host) - - _ = self.validate_port(port=port) - - return f"{host}:{port}" - - -class SourceGrammarTerm(GrammarTerm): - def custom_parsing(self, input: str) -> str: - trimmed = input - if trimmed.startswith("http://"): - trimmed = trimmed.replace("http://", "") - if trimmed.startswith("https://"): - trimmed = trimmed.replace("https://", "") - if trimmed.startswith("github.com/"): - trimmed = trimmed.replace("github.com/", "") - - parts = trimmed.split("/") - if "tree" not in input or len(parts) < 4: - raise BadGrammar( - f"{self.name} should be a valid github.com repo branch url. Try: {self.get_example()}" - ) - - repo = f"{parts[0]}/{parts[1]}" - branch = "/".join(parts[3:]) - - return f"{repo}:{branch}" - - -def validate_arg_count(arg_count: int, verb: GrammarVerb) -> bool: - valid = True - - if arg_count not in verb.abbreviations: - error_str = f"Command {verb.command} supports the following invocations:\n" - for count in sorted(verb.abbreviations.keys()): - abbreviation = verb.abbreviations[count] - example_terms = [] - for i, term_type in enumerate(abbreviation): - if term_type is not None: - term_settings = verb.full_sentence[i] - example = term_settings["klass"](**term_settings).get_example() - example_terms.append(example) - error_str += f"{count} args: {verb.command} {' '.join(example_terms)}\n" - - raise BadGrammar(error_str) - - return valid - - -def launch_shorthand_support(args: tuple) -> tuple: - """When launching, we want to be able to default to 'domain' if it's not provided, to launch - nodes when no name is provided, and to support node names which have multiple words. - - hagrid launch -> hagrid launch domain - hagrid launch United Nations -> hagrid launch "United Nations" domain - hagrid launch United Nations domain -> hagrid launch "United Nations" domain - hagrid launch on docker -> hagrid launch domain on docker - - """ - - # Some mild analysis - found_node_type = False - preposition_position = 10000 - for i, arg in enumerate(args): - if arg in ALLOWED_NODE_TYPES: - found_node_type = True - - if arg.strip() in ["to", "from"]: - if i < preposition_position: - preposition_position = i - - _args = list(args) - - # Default to domain if it's not provided - if not found_node_type: - if preposition_position != 10000: - _args.insert(preposition_position, "domain") - preposition_position += 1 - else: - _args = _args + ["domain"] - - # if there are no prepositions and the domain/network is the last word - if preposition_position == 10000 and _args[-1] in ALLOWED_NODE_TYPES: - _args = [" ".join(_args[:-1])] + _args[-1:] - - # if there are prepositions then combine the words in the name if there are multiple - elif preposition_position != 10000: - _args = [" ".join(_args[: preposition_position - 1])] + _args[ - preposition_position - 1 : - ] - - # if there wasn't a name provided - make sure we don't have an empty place in the list - # so that later logic will generate a name - if _args[0] == "": - _args = _args[1:] - - args = tuple(_args) - - return args - - -def parse_grammar(args: tuple, verb: GrammarVerb) -> list[GrammarTerm]: - # if the command is a launch, check if any shorthands were employed - if verb.command == "launch": - args = launch_shorthand_support(args=args) - - arg_list = list(args) - arg_count = len(arg_list) - errors = [] - if validate_arg_count(arg_count=arg_count, verb=verb): - terms = [] - abbreviation = verb.abbreviations[arg_count] - for i, term_type in enumerate(abbreviation): - if term_type is None: - arg = None # use None so we get the default - else: - arg = arg_list.pop(0) # use a real arg - - term_settings = verb.full_sentence[i] - - try: - term = term_settings["klass"](**term_settings) - term.parse_input(arg) - terms.append(term) - - except BadGrammar as e: - errors.append(str(e)) - - if len(errors) > 0: - raise BadGrammar("\n".join(errors)) - - # make command - return terms - else: - raise BadGrammar("Grammar is not valid") diff --git a/packages/hagrid/hagrid/img/hagrid.png b/packages/hagrid/hagrid/img/hagrid.png deleted file mode 100644 index 2b4dbd75d412c9a7429a6f629fa4b0d2f6cba759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422216 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

vpit&l#tG%)Rm0qXm>OmqPnW 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%0ATZLRML5-5S_Uo*a{anQ$Im~@W=7D0E>m0~ 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%0D}GgY(?dK)Magvfr&!1ZE#*IK=V2mz!sjV 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�@)3g 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 z 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 $wwKX|F99ebzn(?9n8NO5opXNi&cE&<6^S}I^S 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 le3B9*lE2H)>nQrY$IqyJTI$6=`w)@D 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 SnjkP4aC9{vKPpv+zWXm_*4$G7 diff --git a/packages/hagrid/hagrid/img/hagrid2.png b/packages/hagrid/hagrid/img/hagrid2.png deleted file mode 100644 index 8df9d58d147931d758b75bcc0f9806cf1a07e647..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189910 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ⅅojd#v`p)M`WUqDELVCv|2`2@lu4n9 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 Njg`ISYxDbY{{!H_+H?Q_ diff --git a/packages/hagrid/hagrid/land.py b/packages/hagrid/hagrid/land.py deleted file mode 100644 index 1c138c1971b..00000000000 --- a/packages/hagrid/hagrid/land.py +++ /dev/null @@ -1,60 +0,0 @@ -# stdlib - -# relative -from .grammar import GrammarTerm -from .grammar import GrammarVerb -from .grammar import HostGrammarTerm - - -def get_land_verb() -> GrammarVerb: - full_sentence = [ - { - "name": "node_name", - "type": "adjective", - "klass": GrammarTerm, - "example": "'my_domain'", - }, - { - "name": "preposition", - "type": "preposition", - "klass": GrammarTerm, - "default": "at", - "options": ["at", "on"], - }, - { - "name": "host", - "type": "propernoun", - "klass": HostGrammarTerm, - "default": "docker", - "example": "docker", - }, - ] - - abbreviations: dict[int, list[str | None]] = { - 3: [ - "adjective", - "preposition", - "propernoun", - ], # node_name # at # host - 2: [ - "adjective", - None, - "propernoun", - ], # node_name # ignore # host - 1: [ - "adjective", - None, - None, - ], # node_name # ignore # ignore - 0: [ - None, - None, - None, - ], # ignore # ignore # ignore - } - - return GrammarVerb( - command="land", - full_sentence=full_sentence, - abbreviations=abbreviations, - ) diff --git a/packages/hagrid/hagrid/launch.py b/packages/hagrid/hagrid/launch.py deleted file mode 100644 index c6cc785da50..00000000000 --- a/packages/hagrid/hagrid/launch.py +++ /dev/null @@ -1,113 +0,0 @@ -# stdlib - -# relative -from .cache import DEFAULT_BRANCH -from .grammar import ALLOWED_NODE_TYPES -from .grammar import GrammarTerm -from .grammar import GrammarVerb -from .grammar import HostGrammarTerm -from .grammar import SourceGrammarTerm -from .names import random_name - - -def get_launch_verb() -> GrammarVerb: - full_sentence = [ - { - "name": "node_name", - "type": "propernoun", - "klass": GrammarTerm, - "default": random_name, - "example": "'my_domain'", - }, - { - "name": "node_type", - "type": "object", - "klass": GrammarTerm, - "default": "domain", - "options": ALLOWED_NODE_TYPES, - }, - { - "name": "preposition", - "type": "preposition", - "klass": GrammarTerm, - "default": "to", - "options": ["to"], - }, - { - "name": "host", - "type": "propernoun", - "klass": HostGrammarTerm, - "default": "docker", - "example": "docker:8081+", - }, - { - "name": "preposition", - "type": "preposition", - "klass": GrammarTerm, - "default": "from", - "options": ["from"], - }, - { - "name": "source", - "type": "propernoun", - "klass": SourceGrammarTerm, - "default": f"github.com/OpenMined/PySyft/tree/{DEFAULT_BRANCH}", - }, - ] - - abbreviations: dict[int, list[str | None]] = { - 6: [ - "propernoun", # name - "object", # node_type - "preposition", # to - "propernoun", # host - "preposition", # from - "propernoun", # source - ], - 5: [ - None, # name - "object", # node_type - "preposition", # to - "propernoun", # host - "preposition", # from - "propernoun", # source - ], - 4: [ - "propernoun", # name - "object", # node_type - "preposition", # to - "propernoun", # host - None, # ignore - None, # ignore - ], - 3: [ - None, # ignore - "object", # node_type - "preposition", # to - "propernoun", # host - None, # ignore - None, # ignore - ], - 2: [ - "propernoun", # name - "object", # node_type - None, # ignore - None, # ignore - None, # ignore - None, # ignore - ], - 1: [ - None, # ignore - "object", # node_type - None, # ignore - None, # ignore - None, # ignore - None, # ignore - ], - } - - return GrammarVerb( - command="launch", - full_sentence=full_sentence, - abbreviations=abbreviations, - ) diff --git a/packages/hagrid/hagrid/lib.py b/packages/hagrid/hagrid/lib.py deleted file mode 100644 index 057f77160f7..00000000000 --- a/packages/hagrid/hagrid/lib.py +++ /dev/null @@ -1,472 +0,0 @@ -# stdlib -from enum import Enum -import hashlib -import importlib -import importlib.machinery -import importlib.util -import json -import os -from pathlib import Path -import random -import shutil -import socket -import subprocess # nosec - -# third party -import git -import requests -import rich -from rich import console -from rich import progress -from rich.table import Table - -# relative -from .cache import DEFAULT_BRANCH -from .mode import EDITABLE_MODE -from .mode import hagrid_root - - -class GitRemoteProgress(git.RemoteProgress): - # CREDITS: https://splunktool.com/python-progress-bar-for-git-clone - OP_CODES = [ - "BEGIN", - "CHECKING_OUT", - "COMPRESSING", - "COUNTING", - "END", - "FINDING_SOURCES", - "RECEIVING", - "RESOLVING", - "WRITING", - ] - OP_CODE_MAP = { - getattr(git.RemoteProgress, _op_code): _op_code for _op_code in OP_CODES - } - - def __init__(self) -> None: - super().__init__() - self.progressbar = progress.Progress( - progress.SpinnerColumn(), - # *progress.Progress.get_default_columns(), - progress.TextColumn("[progress.description]{task.description}"), - progress.BarColumn(), - progress.TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - "eta", - progress.TimeRemainingColumn(), - progress.TextColumn("{task.fields[message]}"), - console=console.Console(), - transient=False, - ) - self.progressbar.start() - self.active_task = None - - def __del__(self) -> None: - # logger.info("Destroying bar...") - self.progressbar.stop() - - @classmethod - def get_curr_op(cls, op_code: int) -> str: - """Get OP name from OP code.""" - # Remove BEGIN- and END-flag and get op name - op_code_masked = op_code & cls.OP_MASK - return cls.OP_CODE_MAP.get(op_code_masked, "?").title() - - def update( - self, - op_code: int, - cur_count: str | float, - max_count: str | float | None = None, - message: str | None = None, - ) -> None: - # Start new bar on each BEGIN-flag - if op_code & self.BEGIN: - self.curr_op = self.get_curr_op(op_code) - # logger.info("Next: %s", self.curr_op) - self.active_task = self.progressbar.add_task( - description=self.curr_op, - total=max_count, - message=message, - ) - - self.progressbar.update( - task_id=self.active_task, - completed=cur_count, - message=message, - ) - - # End progress monitoring on each END-flag - if op_code & self.END: - # logger.info("Done: %s", self.curr_op) - self.progressbar.update( - task_id=self.active_task, - message=f"[bright_black]{message}", - ) - - -class ProcessStatus(Enum): - RUNNING = "[blue]Running" - DONE = "[green]Done" - FAILED = "[red]Failed" - - -def docker_desktop_memory() -> int: - path = str(Path.home()) + "/Library/Group Containers/group.com.docker/settings.json" - - try: - f = open(path) - out = f.read() - f.close() - return json.loads(out)["memoryMiB"] - - except Exception: # nosec - # docker desktop not found - probably running linux - return -1 - - -def asset_path() -> os.PathLike: - return Path(hagrid_root()) / "hagrid" - - -def manifest_template_path() -> os.PathLike: - return Path(asset_path()) / "manifest_template.yml" - - -def hagrid_cache_dir() -> os.PathLike: - return Path("~/.hagrid").expanduser() - - -def repo_src_path() -> Path: - if EDITABLE_MODE: - return Path(os.path.abspath(Path(hagrid_root()) / "../../")) - else: - return Path(os.path.join(Path(hagrid_cache_dir()) / "PySyft")) - - -def grid_src_path() -> str: - return str(repo_src_path() / "packages" / "grid") - - -def check_is_git(path: Path) -> bool: - is_repo = False - try: - git.Repo(path) - is_repo = True - except Exception: # nosec - pass - return is_repo - - -def is_gitpod() -> bool: - return bool(os.environ.get("GITPOD_WORKSPACE_URL", None)) - - -def gitpod_url(port: int | None = None) -> str: - workspace_url = os.environ.get("GITPOD_WORKSPACE_URL", "") - if port: - workspace_url = workspace_url.replace("https://", f"https://{port}-") - return workspace_url - - -def get_git_repo() -> git.Repo: - # relative - from .art import RichEmoji - - OK_EMOJI = RichEmoji("white_heavy_check_mark").to_str() - - is_git = check_is_git(path=repo_src_path()) - console = rich.get_console() - - if not EDITABLE_MODE and not is_git: - github_repo = "OpenMined/PySyft.git" - git_url = f"https://github.com/{github_repo}" - - print(f"Fetching Syft + Grid Source from {git_url} to {repo_src_path()}") - try: - repo_branch = DEFAULT_BRANCH - repo_path = repo_src_path() - - if repo_path.exists(): - shutil.rmtree(str(repo_path)) - - git.Repo.clone_from( - git_url, - str(repo_path), - single_branch=False, - b=repo_branch, - progress=GitRemoteProgress(), - ) - console.print(f"{OK_EMOJI} Fetched PySyft repo.") - except Exception as e: # nosec - print(f"Failed to clone {git_url} to {repo_src_path()} with error: {e}") - return git.Repo(repo_src_path()) - - -def update_repo(repo: git.Repo, branch: str) -> None: - # relative - from .art import RichEmoji - - OK_EMOJI = RichEmoji("white_heavy_check_mark").to_str() - console = rich.get_console() - if not EDITABLE_MODE: - with console.status("Updating hagrid") as console_status: - console_status.update(f"[bold blue]Updating HAGrid from branch: {branch}") - try: - if repo.is_dirty(): - repo.git.reset("--hard") - repo.remotes.origin.fetch() - repo.git.checkout(branch) - repo.remotes.origin.pull() - console.print(f"{OK_EMOJI} Updated HAGrid from branch: {branch}") - except Exception as e: - print(f"Error checking out branch {branch}.", e) - - -def commit_hash() -> str: - try: - repo = get_git_repo() - sha = repo.head.commit.hexsha - return sha - except Exception as e: - print("failed to get repo sha", e) - return "unknown" - - -def use_branch(branch: str) -> None: - if not EDITABLE_MODE: - print(f"Using HAGrid from branch: {branch}") - repo = get_git_repo() - try: - if repo.is_dirty(): - repo.git.reset("--hard") - repo.remotes.origin.fetch() - repo.git.checkout(branch) - repo.remotes.origin.pull() - except Exception as e: - print(f"Error checking out branch {branch}.", e) - - -def should_provision_remote( - username: str | None, password: str | None, key_path: str | None -) -> bool: - is_remote = username is not None or password is not None or key_path is not None - if username and password or username and key_path: - return is_remote - if is_remote: - raise Exception("--username requires either --password or --key-path") - return is_remote - - -def name_tag(name: str) -> str: - return hashlib.sha256(name.encode("utf8")).hexdigest() - - -def find_available_port( - host: str, port: int | None = None, search: bool = False -) -> int: - if port is None: - port = random.randint(1500, 65000) # nosec - port_available = False - while not port_available: - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result_of_check = sock.connect_ex((host, port)) - - if result_of_check != 0: - port_available = True - break - else: - if search: - port += 1 - else: - break - sock.close() - - except Exception as e: - print(f"Failed to check port {port}. {e}") - sock.close() - - if search is False and port_available is False: - error = ( - f"{port} is in use, either free the port or " - + f"try: {port}+ to auto search for a port" - ) - raise Exception(error) - return port - - -def get_version_module() -> tuple[str, str]: - try: - version_file_path = f"{grid_src_path()}/VERSION" - loader = importlib.machinery.SourceFileLoader("VERSION", version_file_path) - spec = importlib.util.spec_from_loader(loader.name, loader) - if spec: - version_module = importlib.util.module_from_spec(spec) - loader.exec_module(version_module) - version = version_module.get_version() - hash = version_module.get_hash() - return (version, hash) - except Exception as e: - print(f"Failed to retrieve versions from: {version_file_path}. {e}") - return ("unknown", "unknown") - - -# Check base route of an IP address -def check_host(ip: str, silent: bool = False) -> bool: - try: - socket.gethostbyname(ip) - return True - except Exception as e: - if not silent: - print(f"Failed to resolve host {ip}. {e}") - return False - - -# Check status of login page -def check_login_page(ip: str, timeout: int = 30, silent: bool = False) -> bool: - try: - url = f"http://{ip}/login" - response = requests.get(url, timeout=timeout) - if response.status_code == 200: - return True - else: - return False - except Exception as e: - if not silent: - print(f"Failed to check login page {ip}. {e}") - return False - - -# Check api metadata -def check_api_metadata(ip: str, timeout: int = 30, silent: bool = False) -> bool: - try: - url = f"http://{ip}/api/v2/metadata" - response = requests.get(url, timeout=timeout) - if response.status_code == 200: - return True - else: - return False - except Exception as e: - if not silent: - print(f"Failed to check api metadata {ip}. {e}") - return False - - -def save_vm_details_as_json(username: str, password: str, process_list: list) -> None: - """Saves the launched hosts details as json.""" - - host_ip_details: list = [] - - # file path to save host details - dir_path = os.path.expanduser("~/.hagrid") - os.makedirs(dir_path, exist_ok=True) - file_path = f"{dir_path}/host_ips.json" - - for ip_address, _, jupyter_token in process_list: - _data = { - "username": username, - "password": password, - "ip_address": ip_address, - "jupyter_token": jupyter_token, - } - host_ip_details.append(_data) - - # save host details - with open(file_path, "w") as fp: - json.dump({"host_ips": host_ip_details}, fp) - - print(f"Saved vm details at: {file_path}") - - -def generate_user_table(username: str, password: str) -> Table | str: - if not username and not password: - return "" - - table = Table(title="Virtual Machine Credentials") - table.add_column("Username") - table.add_column("Password") - - table.add_row(f"[green]{username}", f"[green]{password}") - - return table - - -def get_process_status(process: subprocess.Popen) -> str: - poll_status = process.poll() - if poll_status is None: - return ProcessStatus.RUNNING.value - elif poll_status != 0: - return ProcessStatus.FAILED.value - else: - return ProcessStatus.DONE.value - - -def generate_process_status_table(process_list: list) -> tuple[Table, bool]: - """Generate a table to show the status of the processes being exected. - - Args: - process_list (list): each item in the list - is a tuple of ip_address, process and jupyter token - - Returns: - Tuple[Table, bool]: table of process status and flag to indicate if all processes are executed. - """ - - process_statuses: list[str] = [] - lines_to_display = 5 # Number of lines to display as output - - table = Table(title="Virtual Machine Status") - table.add_column("PID", style="cyan") - table.add_column("IpAddress", style="magenta") - table.add_column("Status") - table.add_column("Jupyter Token", style="white on black") - table.add_column("Log", overflow="fold", no_wrap=False) - - for ip_address, process, jupyter_token in process_list: - process_status = get_process_status(process) - - process_statuses.append(process_status) - - process_log = [] - if process_status == ProcessStatus.FAILED.value: - process_log += process.stderr.readlines(lines_to_display) - else: - process_log += process.stdout.readlines(lines_to_display) - - process_log_str = "\n".join(log.decode("utf-8") for log in process_log) - process_log_str = process_log_str if process_log else "-" - - table.add_row( - f"{process.pid}", - f"{ip_address}", - f"{process_status}", - f"{jupyter_token}", - f"{process_log_str}", - ) - - processes_completed = ProcessStatus.RUNNING.value not in process_statuses - - return table, processes_completed - - -def check_jupyter_server( - host_ip: str, wait_time: int = 5, silent: bool = False -) -> bool: - if not silent: - print(f"Checking Jupyter Server at VM {host_ip} is up") - - try: - url = f"http://{host_ip}:8888/" - response = requests.get(url, timeout=wait_time) - if response.status_code == 200: - return True - else: - return False - except Exception as e: - if not silent: - print(f"Failed to check jupyter server status {host_ip}. {e}") - return False - - -GIT_REPO = get_git_repo -GRID_SRC_VERSION = get_version_module -GRID_SRC_PATH = grid_src_path diff --git a/packages/hagrid/hagrid/manifest_template.yml b/packages/hagrid/hagrid/manifest_template.yml deleted file mode 100644 index 3ee6afae44e..00000000000 --- a/packages/hagrid/hagrid/manifest_template.yml +++ /dev/null @@ -1,31 +0,0 @@ -manifestVersion: 0.1 -hagrid_version: 0.3.119 -syft_version: 0.8.7-beta.7 -dockerTag: 0.8.7-beta.7 -baseUrl: https://raw.githubusercontent.com/OpenMined/PySyft/ -hash: 90713c314a1ac09cb604d0efa7d414e9811f2691 -target_dir: ~/.hagrid/PySyft/ -files: - grid: - path: packages/grid/ - common: - - default.env - docker: - - default.env - - docker-compose.build.yml - - docker-compose.dev.yml - - docker-compose.pull.yml - - docker-compose.test.yml - - docker-compose.tls.yml - - docker-compose.yml - - traefik/docker/dynamic-tls.yml - - traefik/docker/dynamic.yml - - traefik/docker/traefik-tls.template.yml - - traefik/docker/traefik.yml - k8s: - - devspace.yaml - podman: - - podman/podman-kube/podman-syft-kube-config.yaml - - podman/podman-kube/podman-syft-kube.yaml - - podman/podman-kube/traefik/conf/dynamic.yml - - podman/podman-kube/traefik/traefik.yml diff --git a/packages/hagrid/hagrid/mode.py b/packages/hagrid/hagrid/mode.py deleted file mode 100644 index e21da8ccbba..00000000000 --- a/packages/hagrid/hagrid/mode.py +++ /dev/null @@ -1,47 +0,0 @@ -# stdlib -import os -from pathlib import Path -import site - - -def str_to_bool(bool_str: str | None) -> bool: - result = False - bool_str = str(bool_str).lower() - if bool_str == "true" or bool_str == "1": - result = True - return result - - -def hagrid_root() -> str: - return os.path.abspath(str(Path(__file__).parent.parent)) - - -def is_editable_mode() -> bool: - disable_editable_mode = str_to_bool( - os.environ.get("DISABLE_EDITABLE_MODE", "False") - ) - if disable_editable_mode: - print("🚨 Editable Mode DISABLED") - return False - current_package_root = hagrid_root() - - installed_as_editable = False - sitepackages_dirs = site.getsitepackages() - # check all site-packages returned if they have a hagrid.egg-link - for sitepackages_dir in sitepackages_dirs: - egg_link_file = Path(sitepackages_dir) / "hagrid.egg-link" - try: - linked_folder = egg_link_file.read_text() - # if the current code is in the same path as the egg-link its -e mode - installed_as_editable = current_package_root in linked_folder - break - except Exception: # nosec - pass - - if os.path.exists(Path(current_package_root) / "hagrid.egg-info"): - installed_as_editable = True - - return installed_as_editable - - -EDITABLE_MODE = is_editable_mode() diff --git a/packages/hagrid/hagrid/names.py b/packages/hagrid/hagrid/names.py deleted file mode 100644 index 26e6b92025d..00000000000 --- a/packages/hagrid/hagrid/names.py +++ /dev/null @@ -1,185 +0,0 @@ -# stdlib -from secrets import randbelow - -left_name = [ - "admiring", - "adoring", - "affectionate", - "agitated", - "amazing", - "angry", - "awesome", - "beautiful", - "blissful", - "bold", - "boring", - "brave", - "busy", - "charming", - "clever", - "cool", - "compassionate", - "competent", - "condescending", - "confident", - "cranky", - "crazy", - "dazzling", - "determined", - "distracted", - "dreamy", - "eager", - "ecstatic", - "elastic", - "elated", - "elegant", - "eloquent", - "epic", - "exciting", - "fervent", - "festive", - "flamboyant", - "focused", - "friendly", - "frosty", - "funny", - "gallant", - "gifted", - "goofy", - "gracious", - "great", - "happy", - "hardcore", - "heuristic", - "hopeful", - "hungry", - "infallible", - "inspiring", - "interesting", - "intelligent", - "jolly", - "jovial", - "keen", - "kellis", - "kind", - "laughing", - "loving", - "lucid", - "magical", - "mystifying", - "modest", - "musing", - "naughty", - "nervous", - "nice", - "nifty", - "nostalgic", - "objective", - "optimistic", - "peaceful", - "pedantic", - "pensive", - "practical", - "priceless", - "quirky", - "quizzical", - "recursing", - "relaxed", - "reverent", - "romantic", - "sad", - "serene", - "sharp", - "silly", - "sleepy", - "stoic", - "strange", - "stupefied", - "suspicious", - "sweet", - "tender", - "thirsty", - "trusting", - "unruffled", - "upbeat", - "vibrant", - "vigilant", - "vigorous", - "wizardly", - "wonderful", - "xenodochial", - "youthful", - "zealous", - "zen", -] - -right_name = [ - "altman", - "bach", - "bengios", - "bostrom", - "botvinick", - "brockman", - "chintala", - "chollet", - "chomsky", - "dean", - "dolgov", - "eckersley", - "fridman", - "gardner", - "goertzel", - "goodfellow", - "hassabis", - "he", - "hinton", - "hochreiter", - "hotz", - "howard", - "hutter", - "isbell", - "kaliouby", - "karp", - "karpathy", - "kearns", - "kellis", - "knuth", - "koller", - "krizhevsky", - "larochelle", - "lattner", - "lecun", - "li", - "lim", - "littman", - "malik", - "mironov", - "ng", - "norvig", - "olah", - "pearl", - "pesenti", - "russell", - "salakhutdinov", - "schmidhuber", - "silver", - "smola", - "song", - "sophia", - "sutskever", - "thomas", - "thrun", - "trask", - "vapnik", - "vaswani", - "vinyals", - "winston", - "wolf", - "wolfram", -] - - -def random_name() -> str: - left_i = randbelow(len(left_name) - 1) - right_i = randbelow(len(right_name) - 1) - return f"{left_name[left_i].capitalize()} {right_name[right_i].capitalize()}" diff --git a/packages/hagrid/hagrid/nb_output.py b/packages/hagrid/hagrid/nb_output.py deleted file mode 100644 index c0a4bb0d2fb..00000000000 --- a/packages/hagrid/hagrid/nb_output.py +++ /dev/null @@ -1,15 +0,0 @@ -# future -from __future__ import annotations - - -# alert-info, alert-warning, alert-success, alert-danger -class NBOutput: - def __init__(self, raw_output: str) -> None: - self.raw_output = raw_output - - def _repr_html_(self) -> str: - return self.raw_output - - def to_html(self) -> NBOutput: - self.raw_output = self.raw_output.replace("\n", "
") - return self diff --git a/packages/hagrid/hagrid/parse_template.py b/packages/hagrid/hagrid/parse_template.py deleted file mode 100644 index faa2c143ad6..00000000000 --- a/packages/hagrid/hagrid/parse_template.py +++ /dev/null @@ -1,327 +0,0 @@ -# stdlib -import hashlib -import os -import shutil -from urllib.parse import urlparse - -# third party -from jinja2 import Environment -from jinja2 import FileSystemLoader -from jinja2 import Template -import requests -from rich.progress import track -import yaml - -# relative -from .cache import DEFAULT_REPO -from .cache import STABLE_BRANCH -from .lib import hagrid_cache_dir -from .lib import manifest_template_path -from .lib import repo_src_path -from .mode import EDITABLE_MODE - -HAGRID_TEMPLATE_PATH = str(manifest_template_path()) - - -def read_yml_file(filename: str) -> tuple[dict | None, str]: - template = None - - with open(filename) as fp: - try: - text = fp.read() - template = yaml.safe_load(text) - template_hash = hashlib.sha256(text.encode("utf-8")).hexdigest() - except yaml.YAMLError as exc: - raise exc - - return template, template_hash - - -def read_yml_url(yml_url: str) -> tuple[dict | None, str]: - template = None - - try: - # download file - response = requests.get(yml_url) # nosec - if response.status_code != 200: - raise Exception(f"Failed to download: {yml_url}") - - # Save file to the local destination - try: - template = yaml.safe_load(response.content) - template_hash = hashlib.sha256(response.content).hexdigest() - except yaml.YAMLError as exc: - raise exc - - except Exception as e: - raise e - - return template, template_hash - - -def git_url_for_file(file_path: str, base_url: str, hash: str) -> str: - # url must have unix style slashes - return os.path.join(base_url, hash, file_path).replace(os.sep, "/") - - -def get_local_abs_path(target_dir: str, file_path: str) -> str: - local_path = os.path.join(target_dir, file_path) - return os.path.expanduser(local_path) - - -def is_url(string: str) -> bool: - try: - result = urlparse(string) - return all([result.scheme, result.netloc]) - except ValueError: - return False - - -def is_path(string: str) -> bool: - return os.path.exists(string) - - -def manifest_cache_path(template_hash: str) -> str: - return f"{hagrid_cache_dir()}/manifests/{template_hash}" - - -def url_from_repo(template_location: str | None) -> str | None: - if template_location is None: - return None - - if ":" in template_location and "/" in template_location: - parts = template_location.split(":") - branch_or_hash = parts[1] - repo = parts[0] - elif ":" not in template_location and "/" in template_location: - branch_or_hash = STABLE_BRANCH - repo = template_location - else: - branch_or_hash = template_location - repo = DEFAULT_REPO - - manifest_url = ( - f"https://raw.githubusercontent.com/{repo}/{branch_or_hash}" - "/packages/hagrid/hagrid/manifest_template.yml" - ) - - if is_url(manifest_url): - return manifest_url - return None - - -def get_template_yml(template_location: str | None) -> tuple[dict | None, str]: - if template_location: - if is_url(template_location): - template, template_hash = read_yml_url(template_location) - elif is_path(template_location): - template, template_hash = read_yml_file(template_location) - elif url_from_repo(template_location): - template, template_hash = read_yml_url(url_from_repo(template_location)) - else: - raise Exception(f"{template_location} is not valid") - else: - template_location = HAGRID_TEMPLATE_PATH - - template, template_hash = read_yml_file(template_location) - - if EDITABLE_MODE and is_path(template_location): - # save it to the same folder for dev mode - template_hash = "dev" - return template, template_hash - - -def setup_from_manifest_template( - host_type: str, - deployment_type: str, - template_location: str | None = None, - overwrite: bool = False, - verbose: bool = False, -) -> dict: - template, template_hash = get_template_yml(template_location) - - kwargs_to_parse = {} - - if template is None: - raise ValueError( - f"Failed to read {template_location}. Please check the file name or path is correct." - ) - - git_hash = template["hash"] - git_base_url = template["baseUrl"] - target_dir = manifest_cache_path(template_hash) - all_template_files = template["files"] - docker_tag = template["dockerTag"] - files_to_download = [] - - for package_name in all_template_files: - # Get all files w.r.t that package e.g. grid, syft, hagrid - template_files = all_template_files[package_name] - package_path = template_files["path"] - - # common files - files_to_download += [ - os.path.join(package_path, f) for f in template_files["common"] - ] - - # docker related files - if host_type in ["docker"]: - files_to_download += [ - os.path.join(package_path, f) for f in template_files["docker"] - ] - - # add k8s related files - # elif host_type in ["k8s"]: - # files_to_download += template_files["k8s"] - - else: - raise Exception(f"Hagrid template does not currently support {host_type}.") - - if EDITABLE_MODE and is_path(template_location): - # to test things in editable mode we can pass in a .yml file path and it will - # copy the files instead of download them - for src_file_path in track(files_to_download, description="Copying files"): - full_src_dir = f"{repo_src_path()}/{src_file_path}" - full_target_path = f"{target_dir}/{src_file_path}" - full_target_dir = os.path.dirname(full_target_path) - os.makedirs(full_target_dir, exist_ok=True) - - shutil.copyfile( - full_src_dir, - full_target_path, - ) - else: - download_files( - files_to_download=files_to_download, - git_hash=git_hash, - git_base_url=git_base_url, - target_dir=target_dir, - overwrite=overwrite, - verbose=verbose, - ) - - kwargs_to_parse["tag"] = docker_tag - return kwargs_to_parse - - -def deployment_dir(node_name: str) -> str: - return f"{hagrid_cache_dir()}/deployments/{node_name}" - - -def download_files( - files_to_download: list[str], - git_hash: str, - git_base_url: str, - target_dir: str, - overwrite: bool = False, - verbose: bool = False, -) -> None: - for src_file_path in track(files_to_download, description="Downloading files"): - # For now target file path is same as source file path - trg_file_path = src_file_path - local_destination = get_local_abs_path(target_dir, trg_file_path) - link_to_file = git_url_for_file(src_file_path, git_base_url, git_hash) - download_file( - link_to_file=link_to_file, - local_destination=local_destination, - overwrite=overwrite, - verbose=verbose, - ) - - -def render_templates( - node_name: str, - deployment_type: str, - template_location: str | None, - env_vars: dict, - host_type: str, -) -> None: - template, template_hash = get_template_yml(template_location) - - if template is None: - raise ValueError("Failed to read hagrid template.") - - src_dir = manifest_cache_path(template_hash) - target_dir = deployment_dir(node_name) - all_template_files = template["files"] - - jinja_template = JinjaTemplate(src_dir) - - files_to_render = [] - for package_name in all_template_files: - template_files = all_template_files[package_name] - - # Aggregate all the files to be rendered - - # common files - files_to_render += template_files["common"] - - if host_type in ["docker"]: - # docker related files - for template_file in template_files["docker"]: - if "default.env" not in template_file: - files_to_render.append(template_file) - - # Render the files - for file_path in files_to_render: - folder_path = template_files["path"] - # relative to src_dir - src_file_path = f"{folder_path}{file_path}" - target_file_path = f"{target_dir}/{file_path}" - os.makedirs(os.path.dirname(target_file_path), exist_ok=True) - jinja_template.substitute_vars(src_file_path, env_vars, target_file_path) - - -class JinjaTemplate: - def __init__(self, template_dir: str | os.PathLike) -> None: - self.directory = os.path.expanduser(template_dir) - self.environ = Environment( - loader=FileSystemLoader(self.directory), autoescape=True - ) - - def read_template_from_path(self, filepath: str) -> Template: - return self.environ.get_template(name=filepath) - - def substitute_vars( - self, template_path: str, vars_to_substitute: dict, target_path: str - ) -> None: - template = self.read_template_from_path(template_path) - rendered_template = template.render(vars_to_substitute) - self.save_to(rendered_template, target_path) - - def save_to(self, message: str, filename: str) -> None: - base_dir = self.directory - filepath = os.path.abspath(os.path.join(base_dir, filename)) - - # Create sub directories if does not exist - os.makedirs(os.path.dirname(filepath), exist_ok=True) - - # Save template to filepath - with open(filepath, "w") as fp: - fp.write(message) - - -def download_file( - link_to_file: str, - local_destination: str, - overwrite: bool = False, - verbose: bool = False, -) -> None: - file_dir = os.path.dirname(local_destination) - os.makedirs(file_dir, exist_ok=True) - - if not os.path.exists(local_destination) or overwrite: - try: - # download file - response = requests.get(link_to_file) # nosec - if response.status_code != 200: - raise Exception(f"Failed to download: {link_to_file}") - - # Save file to the local destination - open(local_destination, "wb").write(response.content) - - except Exception as e: - raise e - else: - if verbose: - print(f"Skipping download: {link_to_file} exists.") diff --git a/packages/hagrid/hagrid/quickstart_ui.py b/packages/hagrid/hagrid/quickstart_ui.py deleted file mode 100644 index 9d1f8fc2652..00000000000 --- a/packages/hagrid/hagrid/quickstart_ui.py +++ /dev/null @@ -1,356 +0,0 @@ -# stdlib -from dataclasses import dataclass -import os -from pathlib import Path -import sys -from urllib.parse import urlparse -import zipfile - -# third party -import click -import requests -from tqdm import tqdm - -# relative -from .cache import DEFAULT_BRANCH -from .cache import DEFAULT_REPO -from .cache import arg_cache -from .nb_output import NBOutput - -directory = os.path.expanduser("~/.hagrid/quickstart/") - - -def quickstart_download_notebook( - url: str, directory: str, reset: bool = False, overwrite_all: bool = False -) -> tuple[str, bool, bool]: - os.makedirs(directory, exist_ok=True) - file_name = os.path.basename(url).replace("%20", "_").replace(" ", "_") - file_path = directory + os.sep + file_name - file_path = os.path.abspath(file_path) - - file_exists = os.path.isfile(file_path) - if overwrite_all: - reset = True - - if file_exists and not reset: - response = click.prompt( - f"\nOverwrite {file_name}?", - prompt_suffix="(a/y/N)", - default="n", - show_default=False, - ) - if response.lower() == "a": - reset = True - overwrite_all = True - elif response.lower() == "y": - reset = True - else: - print(f"Skipping {file_name}") - reset = False - - downloaded = False - if not file_exists or file_exists and reset: - print(f"Downloading notebook: {file_name}") - r = requests.get(url, allow_redirects=True) # nosec - with open(os.path.expanduser(file_path), "wb") as f: - f.write(r.content) - downloaded = True - return file_path, downloaded, overwrite_all - - -def fetch_notebooks_for_url( - url: str, - directory: str, - reset: bool = False, - repo: str = DEFAULT_REPO, - branch: str = DEFAULT_BRANCH, - commit: str | None = None, -) -> list[str]: - downloaded_files = [] - allowed_schemes_as_url = ["http", "https"] - url_scheme = urlparse(url).scheme - # relative mode - if url_scheme not in allowed_schemes_as_url: - notebooks = get_urls_from_dir(repo=repo, branch=branch, commit=commit, url=url) - if url.endswith(".ipynb"): - file_name = os.path.basename(url) - url_parts = url.split("notebooks") - if len(url_parts) > 1: - url_dir = url_parts[-1] - else: - url_dir = url - url_dir = url_dir.replace(file_name, "") - else: - url_dir = url - notebook_files = [] - existing_count = 0 - for notebook_url in notebooks: - url_filename = os.path.basename(notebook_url) - url_dirname = os.path.dirname(notebook_url) - if ( - url_dirname.endswith(url_dir) - and os.path.isdir(directory + url_dir) - and os.path.isfile(directory + url_dir + os.sep + url_filename) - ): - notebook_files.append(url_dir + os.sep + url_filename) - existing_count += 1 - - if existing_count > 0: - plural = "s" if existing_count > 1 else "" - print( - f"You have {existing_count} existing notebook{plural} matching: {url}" - ) - for nb in notebook_files: - print(nb) - - overwrite_all = False - for notebook_url in tqdm(notebooks): - file_path, _, overwrite_all = quickstart_download_notebook( - url=notebook_url, - directory=os.path.abspath(directory + os.sep + str(url_dir) + os.sep), - reset=reset, - overwrite_all=overwrite_all, - ) - downloaded_files.append(file_path) - - else: - file_path, _, _ = quickstart_download_notebook( - url=url, directory=directory, reset=reset - ) - downloaded_files.append(file_path) - return downloaded_files - - -def quickstart_extract_notebook( - zip_file: str, - name: str, - directory: Path, - reset: bool = False, - overwrite_all: bool = False, -) -> tuple[str, bool, bool]: - directory.mkdir(exist_ok=True) - reset = overwrite_all - - base_name = os.path.basename(name) - file_path = directory / base_name - file_name = file_path.name - file_exists = file_path.exists() - - if file_exists and not reset: - response = click.prompt( - f"\nOverwrite {file_name}?", - prompt_suffix="(a/y/N)", - default="n", - show_default=False, - ) - if response.lower() == "a": - reset = True - overwrite_all = True - elif response.lower() == "y": - reset = True - else: - print(f"Skipping {file_name}") - reset = False - - extracted = False - if not file_exists or file_exists and reset: - print(f"Extracting notebook: {file_name}") - with zipfile.ZipFile(zip_file, "r") as zf: - zip_info = zf.getinfo(name) - zip_info.filename = base_name - zf.extract(zip_info, directory) - extracted = True - return str(file_path.absolute()), extracted, overwrite_all - - -def fetch_notebooks_from_zipfile( - path: str, directory: str, reset: bool = False -) -> list[str]: - dir_path = Path(directory) - - with zipfile.ZipFile(path, "r") as zf: - notebooks = [f for f in zf.namelist() if f.endswith(".ipynb")] - - notebook_files = [dir_path / os.path.basename(nb) for nb in notebooks] - existing_files = [nb for nb in notebook_files if nb.exists()] - - existing_count = len(existing_files) - - if existing_count > 0: - plural = "s" if existing_count > 1 else "" - print(f"You have {existing_count} existing notebook{plural}") - for nb in existing_files: - print(nb) - - extracted_files = [] - overwrite_all = False - for notebook in tqdm(notebooks): - file_path, _, overwrite_all = quickstart_extract_notebook( - zip_file=path, - name=notebook, - directory=dir_path, - reset=reset, - overwrite_all=overwrite_all, - ) - extracted_files.append(file_path) - - return extracted_files - - -@dataclass -class Tutorial: - filename: str - description: str - url: str - - -REPO_RAW_PATH = "https://raw.githubusercontent.com/OpenMined/PySyft" - -TUTORIALS = { - "api/0.8": Tutorial( - filename="api/0.8", - description="0.8 API Notebooks", - url="api/0.8", - ), - "hello-syft": Tutorial( - filename="tutorials/hello-syft", - description="Hello Syft", - url="tutorials/hello-syft", - ), - "data-engineer": Tutorial( - filename="tutorials/data-engineer", - description="Data Engineer", - url="tutorials/data-engineer", - ), - "data-owner": Tutorial( - filename="tutorials/data-owner", - description="Data Owner", - url="tutorials/data-owner", - ), - "data-scientist": Tutorial( - filename="tutorials/data-scientist", - description="Data Scientist", - url="tutorials/data-scientist", - ), - "pandas-cookbook": Tutorial( - filename="tutorials/pandas-cookbook", - description="Pandas Cookbook", - url="tutorials/pandas-cookbook", - ), -} - - -class QuickstartUI: - @property - def tutorials(self) -> dict[str, Tutorial]: - return TUTORIALS - - def download( - self, tutorial_name: str, reset: bool = False, branch: str = "dev" - ) -> NBOutput: - if tutorial_name not in TUTORIALS.keys(): - return NBOutput( - f'

' - ) - else: - tutorial = TUTORIALS[tutorial_name] - downloaded_files = fetch_notebooks_for_url( - url=tutorial.url, directory=directory, branch=branch - ) - html = "" - if len(downloaded_files) == 0: - html += f'
{tutorial_name} failed to download.' - else: - first = downloaded_files[0] - jupyter_path = first.replace(os.path.abspath(directory) + "/", "") - - html += f'' - return NBOutput(html) - - def _repr_html_(self) -> str: - html = "" - if not arg_cache["install_wizard_complete"]: - html += "

Step 1b: Install πŸ§™πŸ½β€β™‚οΈ Wizard (Recommended)

" - html += ( - "It looks like this might be your first time running Quickstart.
" - ) - html += ( - "
Please go through the Install Wizard notebook to " - + "install Syft and optionally start a Grid server." - ) - html += ( - '
πŸ“– Click to start ' - + "Install πŸ§™πŸ½β€β™‚οΈ Wizard
" - ) - html += "
" - - html += "

Download Tutorials

" - html += "Below is a list of tutorials to download using quickstart.
" - html += "
    " - for name, tutorial in TUTORIALS.items(): - html += ( - "
  • πŸ“– Tutorial Series: " - + f"{name}
    {tutorial.description}
  • " - ) - html += "
" - first = list(TUTORIALS.keys())[0] - html += ( - "
Try running:
" - + f'quickstart.download("{first}")
' - ) - - return html - - -def get_urls_from_dir( - url: str, - repo: str, - branch: str, - commit: str | None = None, -) -> list[str]: - notebooks = [] - slug = commit if commit else branch - - gh_api_call = ( - "https://api.github.com/repos/" + repo + "/git/trees/" + slug + "?recursive=1" - ) - r = requests.get(gh_api_call) # nosec - if r.status_code != 200: - print( - f"Failed to fetch notebook from: {gh_api_call}.\n" - + "Please try again with the correct parameters!" - ) - sys.exit(1) - - res = r.json() - - for file in res["tree"]: - if file["path"].startswith("notebooks/quickstart/" + url) or file[ - "path" - ].startswith("notebooks/" + url): - if file["path"].endswith(".ipynb"): - temp_url = ( - "https://raw.githubusercontent.com/" - + repo - + "/" - + slug - + "/" - + file["path"] - ) - notebooks.append(temp_url) - - if len(notebooks) == 0: - for file in res["tree"]: - if file["path"].startswith("notebooks/" + url): - if file["path"].endswith(".ipynb"): - temp_url = ( - "https://raw.githubusercontent.com/" - + repo - + "/" - + slug - + "/" - + file["path"] - ) - notebooks.append(temp_url) - return notebooks diff --git a/packages/hagrid/hagrid/rand_sec.py b/packages/hagrid/hagrid/rand_sec.py deleted file mode 100644 index 3323554a72f..00000000000 --- a/packages/hagrid/hagrid/rand_sec.py +++ /dev/null @@ -1,84 +0,0 @@ -# stdlib -from os import urandom -import string -import sys - - -def generate_sec_random_password( - length: int, - special_chars: bool = True, - digits: bool = True, - lower_case: bool = True, - upper_case: bool = True, -) -> str: - """Generates a random password of the given length. - - Args: - length (int): length of the password - special_chars (bool, optional): Include at least one specials char in the password. Defaults to True. - digits (bool, optional): Include at least one digit in the password. Defaults to True. - lower_case (bool, optional): Include at least one lower case character in the password. Defaults to True. - upper_case (bool, optional): Includde at least one upper case character in the password. Defaults to True. - - Raises: - ValueError: If password length if too short. - - Returns: - str: randomly generated password - """ - if not isinstance(length, int) or length < 10: - raise ValueError( - "Password should have a positive safe length of at least 10 characters!" - ) - - choices: str = "" - required_tokens: list[str] = [] - if special_chars: - special_characters = "!@#$%^&*()_+" - choices += special_characters - required_tokens.append( - special_characters[ - int.from_bytes(urandom(1), sys.byteorder) % len(special_characters) - ] - ) - if lower_case: - choices += string.ascii_lowercase - required_tokens.append( - string.ascii_lowercase[ - int.from_bytes(urandom(1), sys.byteorder) % len(string.ascii_lowercase) - ] - ) - if upper_case: - choices += string.ascii_uppercase - required_tokens.append( - string.ascii_uppercase[ - int.from_bytes(urandom(1), sys.byteorder) % len(string.ascii_uppercase) - ] - ) - if digits: - choices += string.digits - required_tokens.append( - string.digits[ - int.from_bytes(urandom(1), sys.byteorder) % len(string.digits) - ] - ) - - # Python 3 (urandom returns bytes) - password = [choices[c % len(choices)] for c in urandom(length)] - - # Pick some random indexes - random_indexes: set[int] = set() - while len(random_indexes) < len(required_tokens): - random_indexes.add(int.from_bytes(urandom(1), sys.byteorder) % len(password)) - - # Replace the random indexes with the required tokens - for i, idx in enumerate(random_indexes): - password[idx] = required_tokens[i] - - return "".join(password) - - -if __name__ == "__main__": - pwd_length = 48 - # generate_sec_random_password(pwd_length) - print(generate_sec_random_password(pwd_length, special_chars=False)) diff --git a/packages/hagrid/hagrid/stable_version.py b/packages/hagrid/hagrid/stable_version.py deleted file mode 100644 index d596aae77cd..00000000000 --- a/packages/hagrid/hagrid/stable_version.py +++ /dev/null @@ -1 +0,0 @@ -LATEST_STABLE_SYFT = "0.8.6" diff --git a/packages/hagrid/hagrid/style.py b/packages/hagrid/hagrid/style.py deleted file mode 100644 index f42d9f79061..00000000000 --- a/packages/hagrid/hagrid/style.py +++ /dev/null @@ -1,44 +0,0 @@ -# stdlib -import io - -# third party -import click -import rich - -# relative -from .deps import DEPENDENCIES -from .mode import EDITABLE_MODE - - -class RichGroup(click.Group): - def format_usage( - self, ctx: click.core.Context, formatter: click.formatting.HelpFormatter - ) -> None: - sio = io.StringIO() - console = rich.get_console() - mode = "" - if EDITABLE_MODE: - mode = "[bold red]EDITABLE DEV MODE[/bold red] :police_car_light:" - console.print( - "[bold red]HA[/bold red][bold magenta]Grid[/bold magenta]!", ":mage:", mode - ) - table = rich.table.Table() - - table.add_column("Dependency", style="magenta") - table.add_column("Found", justify="right") - - for dep in sorted(DEPENDENCIES.keys()): - path = DEPENDENCIES[dep] - installed_str = ":white_check_mark:" if path is not None else ":cross_mark:" - dep_emoji = ":gear:" - if dep == "docker": - dep_emoji = ":whale:" - if dep == "git": - dep_emoji = ":file_folder:" - if dep == "ansible-playbook": - dep_emoji = ":blue_book:" - table.add_row(f"{dep_emoji} {dep}", installed_str) - # console.print(dep_emoji, dep, installed_str) - console.print(table) - console.print("Usage: hagrid [OPTIONS] COMMAND [ARGS]...") - formatter.write(sio.getvalue()) diff --git a/packages/hagrid/hagrid/util.py b/packages/hagrid/hagrid/util.py deleted file mode 100644 index 73d1cf1e34e..00000000000 --- a/packages/hagrid/hagrid/util.py +++ /dev/null @@ -1,102 +0,0 @@ -# stdlib -from collections.abc import Callable -from enum import Enum -import os -import subprocess # nosec -import sys -from typing import Any -from urllib.parse import urlparse - -# relative -from .dummynum import DummyNum - - -class NodeSideType(str, Enum): - LOW_SIDE = "low" - HIGH_SIDE = "high" - - def __str__(self) -> str: - # Use values when transforming NodeType to str - return self.value - - -class ImportFromSyft: - @staticmethod - def import_syft_error() -> Callable: - try: - # syft absolute - from syft.service.response import SyftError - except Exception: - SyftError = DummyNum - - return SyftError - - @staticmethod - def import_stage_protocol_changes() -> Callable: - try: - # syft absolute - from syft.protocol.data_protocol import stage_protocol_changes - except Exception: - - def stage_protocol_changes(*args: Any, **kwargs: Any) -> None: - pass - - return stage_protocol_changes - - @staticmethod - def import_node_type() -> Callable: - try: - # syft absolute - from syft.abstract_node import NodeType - except Exception: - NodeType = DummyNum - - return NodeType - - -def from_url(url: str) -> tuple[str, str, int, str, Any | str]: - try: - # urlparse doesnt handle no protocol properly - if "://" not in url: - url = "http://" + url - parts = urlparse(url) - host_or_ip_parts = parts.netloc.split(":") - # netloc is host:port - port = 80 - if len(host_or_ip_parts) > 1: - port = int(host_or_ip_parts[1]) - host_or_ip = host_or_ip_parts[0] - return ( - host_or_ip, - parts.path, - port, - parts.scheme, - getattr(parts, "query", ""), - ) - except Exception as e: - print(f"Failed to convert url: {url} to GridURL. {e}") - raise e - - -def fix_windows_virtualenv_api(cls: type) -> None: - # fix bug in windows - def _python_rpath(self: Any) -> str: - """The relative path (from environment root) to python.""" - # Windows virtualenv installation installs pip to the [Ss]cripts - # folder. Here's a simple check to support: - if sys.platform == "win32": - # fix here https://github.com/sjkingo/virtualenv-api/issues/47 - return os.path.join(self.path, "Scripts", "python.exe") - return os.path.join("bin", "python") - - cls._python_rpath = property(_python_rpath) - - -def shell(command: str) -> str: - try: - output = subprocess.check_output( # nosec - command, shell=True, stderr=subprocess.STDOUT - ) - except Exception: - output = b"" - return output.decode("utf-8") diff --git a/packages/hagrid/hagrid/version.py b/packages/hagrid/hagrid/version.py deleted file mode 100644 index 0021ed1ffdf..00000000000 --- a/packages/hagrid/hagrid/version.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 -# HAGrid Version -__version__ = "0.3.119" - -if __name__ == "__main__": - print(__version__) diff --git a/packages/hagrid/hagrid/win_bootstrap.py b/packages/hagrid/hagrid/win_bootstrap.py deleted file mode 100644 index 9cd79c24c36..00000000000 --- a/packages/hagrid/hagrid/win_bootstrap.py +++ /dev/null @@ -1,267 +0,0 @@ -# stdlib -from collections.abc import Callable -import subprocess # nosec - -# one liner to use bootstrap script: -# CMD: curl https://raw.githubusercontent.com/OpenMined/PySyft/dev/packages/hagrid/hagrid/win_bootstrap.py > win_bootstrap.py && python win_bootstrap.py # noqa -# Powershell is complaining about a utf-8 issue we need to fix, could be related to a -# bug with long lines in utf-8 -# PS: $r = Invoke-WebRequest "https://raw.githubusercontent.com/OpenMined/PySyft/dev/packages/hagrid/hagrid/win_bootstrap.py" -UseBasicParsing; echo $r.Content > win_bootstrap.py; python win_bootstrap.py # noqa - - -class Requirement: - def __init__( - self, full_name: str, choco_name: str, detect: Callable, extras: str = "" - ) -> None: - self.full_name = full_name - self.choco_name = choco_name - self.detect = detect - self.extras = extras - - def __repr__(self) -> str: - return self.full_name - - -install_choco_pwsh = """ -[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; -Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); -""" - -install_wsl2_pwsh = """ -wsl --update; wsl --shutdown; wsl --set-default-version 2; wsl --install -d Ubuntu; wsl --setdefault Ubuntu; -""" - -# add this to block powershell from existing for debugging -# Read-Host -Prompt string - - -def make_admin_cmd(admin_cmd: str) -> str: - return ( - f"Start-Process PowerShell -Wait -Verb RunAs -ArgumentList " - '"' - "Set-ExecutionPolicy Bypass -Scope Process -Force; " - f"{admin_cmd}; " - '"' - ) - - -def where_is(binary: str, req: Requirement) -> bool: - print(f"{req.full_name} - {binary}", end="", flush=True) - found = path_where_is(binary) - if not found: - found = full_where_is(binary) - if found: - print(" √") - else: - print(" Γ—") - return found - - -def path_where_is(binary: str) -> bool: - try: - cmds = ["where.exe", binary] - output = subprocess.run(cmds, capture_output=True, cwd="C:\\") # nosec - out = str(output.stdout.decode("utf-8")).split("\r\n") - if binary in out[0]: - return True - except Exception as e: - print("error", e) - pass - return False - - -def full_where_is(binary: str) -> bool: - try: - powershell_cmd = f"where.exe /R C:\ *.exe | findstr \\{binary}$" # noqa: W605 - cmds = ["powershell.exe", "-Command", powershell_cmd] - output = subprocess.run(cmds, capture_output=True, cwd="C:\\") # nosec - out = str(output.stdout.decode("utf-8")).split("\r\n") - if binary in out[0]: - return True - except Exception as e: - print("error", e) - pass - return False - - -def exe(binary: str) -> Callable: - def call(req: Requirement) -> bool: - return where_is(binary=binary, req=req) - - return call - - -def detect_wsl2(req: Requirement) -> bool: - print(f"{req.full_name} - wsl.exe ", end="") - try: - powershell_cmd = "wsl.exe --status" - cmds = ["powershell.exe", "-Command", powershell_cmd] - output = subprocess.run(cmds, capture_output=True) # nosec - out = output.stdout.decode("utf-16") - if "Default Distribution: Ubuntu" in out: - pass - if "Default Version: 2" in out: - print(" √") - return True - except Exception as e: - print("error", e) - pass - print(" Γ—") - return False - - -requirements = [] -requirements.append( - Requirement( - full_name="Windows Subsystem for Linux 2", - choco_name="wsl2", - detect=detect_wsl2, - ) -) -requirements.append( - Requirement( - full_name="Chocolatey Package Manager", - choco_name="choco", - detect=exe("choco.exe"), - ) -) -requirements.append( - Requirement( - full_name="Anaconda Individual Edition", - choco_name="anaconda3", - detect=exe("conda.exe"), - ) -) -requirements.append( - Requirement( - full_name="Git Version Control", - choco_name="git", - detect=exe("git.exe"), - ) -) -requirements.append( - Requirement( - full_name="Docker Desktop", - choco_name="docker-desktop", - detect=exe("docker.exe"), - ) -) - - -def install_elevated_powershell(full_name: str, powershell_cmd: str) -> None: - try: - input( - f"\nInstalling {full_name} requires Administrator.\n" - "When the UAC dialogue appears click Yes on the left.\n\n" - "Press enter to start..." - ) - powershell_cmds = ["-command", powershell_cmd] - output = subprocess.run( # nosec - ["powershell.exe"] + powershell_cmds, capture_output=True - ) - _ = output.stdout.decode("utf-8") - except Exception as e: - print("failed", e) - - -def install_choco() -> None: - return install_elevated_powershell( - full_name="Chocolatey", powershell_cmd=make_admin_cmd(install_choco_pwsh) - ) - - -def install_wsl2() -> None: - return install_elevated_powershell( - full_name="WSL2", powershell_cmd=make_admin_cmd(install_wsl2_pwsh) - ) - - -def install_deps(requirements: list[Requirement]) -> None: - package_names = [] - for req in requirements: - package_names.append(req.choco_name) - - try: - input( - "\nInstalling packages requires Administrator.\n" - "When the UAC dialogue appears click Yes on the left.\n\n" - "Press enter to start..." - ) - choco_args = f"choco.exe install {' '.join(package_names)} -y" - powershell_cmds = ["-command", make_admin_cmd(choco_args)] - output = subprocess.run( # nosec - ["powershell.exe"] + powershell_cmds, capture_output=True - ) - _ = str(output.stdout.decode("utf-8")) - except Exception as e: - print("failed", e) - - -def ask_install(requirement: Requirement) -> bool: - val = input(f"Do you want to install {requirement.full_name} (Y/n): ") - if "y" in val.lower(): - return True - return False - - -def check_all(requirements: list[Requirement]) -> list[Requirement]: - missing = [] - for req in requirements: - if not req.detect(req): - missing.append(req) - return missing - - -def main() -> None: - print("\nHAGrid Windows Dependency Installer") - print("===================================\n") - print("Searching your computer for:") - missing_deps = check_all(requirements=requirements) - - if len(missing_deps) > 0: - print("\nWe were unable to find the following dependencies:") - print("-----------------------------------") - for dep in missing_deps: - print(f"{dep.full_name}") - - print("") - desired = [] - choco_required = False - wsl2_required = False - for dep in missing_deps: - if ask_install(dep): - if dep.choco_name == "choco": - choco_required = True - elif dep.choco_name == "wsl2": - wsl2_required = True - else: - desired.append(dep) - elif dep.choco_name == "choco": - print("You must install Chocolatey to install other dependencies") - return - - if wsl2_required: - install_wsl2() - - if choco_required: - install_choco() - - if len(desired) > 0: - install_deps(desired) - - print("") - still_missing = check_all(requirements=missing_deps) - if len(still_missing) > 0: - print("We were still unable to find the following dependencies:") - print("-----------------------------------") - for dep in still_missing: - print(f"{dep.full_name}") - print("Please try again.") - else: - print("\nCongratulations. All done.") - print("===================================\n") - print("Now you can run HAGrid on Windows!") - - -if __name__ == "__main__": - main() diff --git a/packages/hagrid/hagrid/wizard_ui.py b/packages/hagrid/hagrid/wizard_ui.py deleted file mode 100644 index 7f4c5c1c0d4..00000000000 --- a/packages/hagrid/hagrid/wizard_ui.py +++ /dev/null @@ -1,62 +0,0 @@ -# stdlib - -# relative -from .cache import arg_cache -from .deps import Dependency -from .deps import check_grid_docker -from .deps import check_hagrid -from .deps import check_syft -from .deps import check_syft_deps -from .nb_output import NBOutput - -steps = {} -steps["check_hagrid"] = False -steps["check_syft"] = False -steps["check_grid"] = False - - -def complete_install_wizard( - output: dict[str, Dependency] | NBOutput, -) -> dict[str, Dependency] | NBOutput: - flipped = arg_cache["install_wizard_complete"] - if not flipped: - for _, v in steps.items(): - if v is False: - return output - arg_cache["install_wizard_complete"] = True - if isinstance(output, NBOutput): - if flipped != arg_cache["install_wizard_complete"]: - output.raw_output += "\n\nβœ… You have completed the Install Wizard" - return output - - -class WizardUI: - @property - def check_hagrid(self) -> dict[str, Dependency] | NBOutput: - steps["check_hagrid"] = True - return complete_install_wizard(check_hagrid()) - - @property - def check_syft_deps(self) -> dict[str, Dependency] | NBOutput: - steps["check_syft"] = True - return complete_install_wizard(check_syft_deps()) - - @property - def check_syft(self) -> dict[str, Dependency] | NBOutput: - steps["check_syft"] = True - return complete_install_wizard(check_syft()) - - @property - def check_syft_pre(self) -> dict[str, Dependency] | NBOutput: - steps["check_syft"] = True - return complete_install_wizard(check_syft(pre=True)) - - @property - def check_grid_docker(self) -> dict[str, Dependency] | NBOutput: - print("Deprecated. Please use .check_docker") - return self.check_docker - - @property - def check_docker(self) -> dict[str, Dependency] | NBOutput: - steps["check_grid"] = True - return complete_install_wizard(check_grid_docker()) diff --git a/packages/hagrid/scripts/install.sh b/packages/hagrid/scripts/install.sh deleted file mode 100755 index aa13f184093..00000000000 --- a/packages/hagrid/scripts/install.sh +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/sh - -# run with: -# curl https://raw.githubusercontent.com/OpenMined/PySyft/dev/packages/hagrid/scripts/install.sh | sh - -set -e - -cat /dev/null </dev/null - RETURN_CODE=$? - set +e - return $RETURN_CODE -} - -check_ubuntu() { - if [ -f /etc/os-release ] - then - . /etc/os-release - if [ "$ID" = "ubuntu" ] - then - return 0 - fi - fi - return 1 -} - -check_macos() { - if [ "$(uname)" = "Darwin" ] - then - return 0 - fi - return 1 -} - -check_os_supported() { - echo "Checking OS..." - if check_macos - then - echo "βœ… macOS detected" - return 0 - elif check_ubuntu - then - echo "βœ… Ubuntu detected" - return 0 - fi - echo $OS_NOT_SUPPORTED - exit 1 -} - -apt_install() { - execute_sudo "apt-get -qq -o=Dpkg::Use-Pty=0 update -y" - execute_sudo "apt-get -qq -o=Dpkg::Use-Pty=0 install $1 -y" -} - -brew_install() { - if is_command "brew" - then - # echo "Would run: brew install $1" - HOMEBREW_NO_AUTO_UPDATE=1 brew install $1 - else - echo "\nWe require brew to install packages.\nYou must install brew first: https://brew.sh/" - exit 1 - fi -} - -hagrid_install() { - echo "\nChecking hagrid ..." - if is_command "hagrid" - then - echo "βœ… hagrid detected" - else - echo "Installing hagrid" - pip install --quiet -U hagrid - fi -} - -# from: https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -unset HAVE_SUDO_ACCESS # unset this from the environment - -abort() { - printf "%s\n" "$@" >&2 - exit 1 -} - - -have_sudo_access() { - if [[ ! -x "/usr/bin/sudo" ]] - then - return 1 - fi - - local -a SUDO=("/usr/bin/sudo") - if [[ -n "${SUDO_ASKPASS-}" ]] - then - SUDO+=("-A") - elif [[ -n "${NONINTERACTIVE-}" ]] - then - SUDO+=("-n") - fi - - if [[ -z "${HAVE_SUDO_ACCESS-}" ]] - then - if [[ -n "${NONINTERACTIVE-}" ]] - then - "${SUDO[@]}" -l mkdir &>/dev/null - else - "${SUDO[@]}" -v && "${SUDO[@]}" -l mkdir &>/dev/null - fi - HAVE_SUDO_ACCESS="$?" - fi - - if [[ -n "${HOMEBREW_ON_MACOS-}" ]] && [[ "${HAVE_SUDO_ACCESS}" -ne 0 ]] - then - abort "Need sudo access on macOS (e.g. the user ${USER} needs to be an Administrator)!" - fi - - return "${HAVE_SUDO_ACCESS}" -} - -execute() { - if ! "$@" - then - abort "$(printf "Failed during: %s" "$(shell_join "$@")")" - fi -} - -execute_sudo() { - local -a args=("$@") - if have_sudo_access - then - if [[ -n "${SUDO_ASKPASS-}" ]] - then - args=("-A" "${args[@]}") - fi - echo "/usr/bin/sudo" "${args[@]}" - execute "/usr/bin/sudo" "${args[@]}" - else - echo "${args[@]}" - execute "${args[@]}" - fi -} - -check_and_install() { - echo "\nChecking $1 ..." - if is_command $1 - then - echo "βœ… $1 detected" - return 0 - else - echo "Installing missing dependency $2" - if check_macos - then - brew_install $2 - elif check_ubuntu - then - apt_install $2 - fi - fi - - if is_command $1 - then - echo "βœ… $1 detected" - return 0 - else - echo "Failed to install $1. Please manually install it." - fi -} - -check_install_python() { - check_and_install python3 python3 -} - -check_install_pip() { - echo "\nChecking pip ..." - if is_command "pip" - then - echo "βœ… pip detected" - return 0 - else - if check_macos - then - echo "Installing missing dependency pip" - python3 -m ensurepip - else - check_and_install pip python3-pip - fi - fi - - if is_command "pip" - then - echo "βœ… pip detected" - else - echo "Failed to install pip. Please manually install it." - fi -} - -check_install_git() { - check_and_install git git -} - -execute() { - check_os_supported - check_install_python - check_install_pip - check_install_git - - hagrid_install - - if is_command "hagrid" - then - echo "\nπŸ§™β€β™‚οΈ HAGrid is installed!\n" - echo "To get started run: \n$ hagrid quickstart\n" - else - echo "\nHAGrid failed to install. Please try manually with:" - echo "pip install -U hagrid" - exit 1 - fi -} - -execute diff --git a/packages/hagrid/scripts/update_manifest.py b/packages/hagrid/scripts/update_manifest.py deleted file mode 100644 index 4f31428c2ab..00000000000 --- a/packages/hagrid/scripts/update_manifest.py +++ /dev/null @@ -1,47 +0,0 @@ -# stdlib -import os -import subprocess -import sys - -# third party -import yaml - - -def latest_commit_id() -> str: - cmd = 'git log --format="%H" -n 1' - commit_id = subprocess.check_output(cmd, shell=True) - return commit_id.decode("utf-8").strip() - - -def update_manifest(docker_tag: str | None) -> None: - """Update manifest_template file with latest commit hash.""" - - # Get latest commit id - commit_id = latest_commit_id() - - template_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), "../../")) - template_filepath = os.path.join(template_dir, "hagrid/manifest_template.yml") - - # open the manifest file - with open(template_filepath) as stream: - template_dict = yaml.safe_load(stream) - - # update commit id - template_dict["hash"] = commit_id - - # update docker tag if available - if docker_tag: - template_dict["dockerTag"] = docker_tag - - # save manifest file - with open(template_filepath, "w") as fp: - yaml.dump(template_dict, fp, sort_keys=False) - - -if __name__ == "__main__": - docker_tag = None - - if len(sys.argv) > 1: - docker_tag = sys.argv[1] - - update_manifest(docker_tag) # Update manifest file diff --git a/packages/hagrid/setup.py b/packages/hagrid/setup.py deleted file mode 100644 index 5f127f6a25c..00000000000 --- a/packages/hagrid/setup.py +++ /dev/null @@ -1,47 +0,0 @@ -# stdlib -import platform - -# third party -from setuptools import find_packages -from setuptools import setup - -__version__ = "0.3.119" - -DATA_FILES = {"img": ["hagrid/img/*.png"], "hagrid": ["*.yml"]} - -packages = [ - "ascii_magic", - "click>=8.1.7", - "cryptography>=41.0.4", - "gitpython", - "jinja2", - "names", - "packaging>=23.0", - "paramiko", - "pyOpenSSL>=23.2.0", - "requests", - "rich", - "setuptools", - "virtualenv-api", - "virtualenv", - "PyYAML", - "tqdm", - "gevent>=22.10.2,<=23.9.1", -] - -if platform.system().lower() != "windows": - packages.extend(["ansible", "ansible-core"]) - -setup( - name="hagrid", - description="Happy Automation for Grid", - long_description="HAGrid is the swiss army knife of OpenMined's PySyft and PyGrid.", - long_description_content_type="text/plain", - version=__version__, - author="Andrew Trask ", - packages=find_packages(), - package_data=DATA_FILES, - install_requires=packages, - include_package_data=True, - entry_points={"console_scripts": ["hagrid = hagrid.cli:cli"]}, -) diff --git a/packages/hagrid/tests/__init__.py b/packages/hagrid/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/hagrid/tests/hagrid/__init__.py b/packages/hagrid/tests/hagrid/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/hagrid/tests/hagrid/cli_test.py b/packages/hagrid/tests/hagrid/cli_test.py deleted file mode 100644 index 346988d527f..00000000000 --- a/packages/hagrid/tests/hagrid/cli_test.py +++ /dev/null @@ -1,199 +0,0 @@ -# stdlib -from collections import defaultdict - -# third party -from hagrid import cli -from hagrid import grammar - - -def test_hagrid_launch() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch' whenever they want to spin - up a new node with a randomly chosen name""" - - # COMMAND: "hagrid launch" - args: list[str] = [] - - verb = cli.get_launch_verb() - grammar = cli.parse_grammar(args=tuple(args), verb=verb) - verb.load_grammar(grammar=grammar) - cmd = cli.create_launch_cmd( - verb=verb, kwargs=defaultdict(lambda: None), ignore_docker_version_check=True - ) - - cmd = cmd["Launching"][0] # type: ignore - - # check that it's a domain by default - assert "NODE_TYPE=domain" in cmd or "NODE_TYPE='domain'" in cmd - - # check that the node has a name - assert "NODE_NAME=" in cmd - - # check that tail is on by default - assert " -d " not in cmd - - -def test_shortand_parse() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch' whenever they want to spin - up a new node with a randomly chosen name.""" - - # COMMAND: "hagrid launch" - args: tuple = () - args = grammar.launch_shorthand_support(args) - - # check that domain gets added to the end of the command - assert args == ("domain",) - - -def test_hagrid_launch_without_name_with_preposition() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch' whenever they want to spin - up a new node with a randomly chosen name""" - - # COMMAND: "hagrid launch on docker" - args: list[str] = ["to", "docker"] - - verb = cli.get_launch_verb() - grammar = cli.parse_grammar(args=tuple(args), verb=verb) - verb.load_grammar(grammar=grammar) - cmd = cli.create_launch_cmd( - verb=verb, kwargs=defaultdict(lambda: None), ignore_docker_version_check=True - ) - - cmd = cmd["Launching"][0] # type: ignore - # check that it's a domain by default - assert "NODE_TYPE=domain" in cmd or "NODE_TYPE='domain'" in cmd - - # check that the node has a name - assert "NODE_NAME=" in cmd - - # check that tail is on by default - assert " -d " not in cmd - - -def test_shortand_parse_without_name_with_preposition() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch' whenever they want to spin - up a new node with a randomly chosen name.""" - - # COMMAND: "hagrid launch" - args: tuple[str, ...] = ("to", "docker") - args = grammar.launch_shorthand_support(args) - - # check that domain gets added to the end of the command - assert args == ("domain", "to", "docker") - - -def test_launch_with_multiword_domain_name() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch' whenever they want to spin - up a new node with a randomly chosen name""" - - # COMMAND: "hagrid launch United Nations" - args: list[str] = ["United", "Nations"] - - verb = cli.get_launch_verb() - grammar = cli.parse_grammar(args=tuple(args), verb=verb) - verb.load_grammar(grammar=grammar) - cmd = cli.create_launch_cmd( - verb=verb, kwargs=defaultdict(lambda: None), ignore_docker_version_check=True - ) - - cmd = cmd["Launching"][0] # type: ignore - - # check that it's a domain by default - assert "NODE_TYPE=domain" in cmd or "NODE_TYPE='domain'" in cmd - - # check that the node has a name - assert "NODE_NAME=united_nations" in cmd or "NODE_NAME='united_nations'" in cmd - - # check that tail is on by default - assert " -d " not in cmd - - -def test_launch_with_longer_multiword_domain_name() -> None: - """This test is important because we want to make it convenient for users to launch nodes with - an arbitrary number of words.""" - - # COMMAND: "hagrid launch United Nations" - args: list[str] = ["United", "States", "of", "America"] - - verb = cli.get_launch_verb() - grammar = cli.parse_grammar(args=tuple(args), verb=verb) - verb.load_grammar(grammar=grammar) - cmd = cli.create_launch_cmd( - verb=verb, kwargs=defaultdict(lambda: None), ignore_docker_version_check=True - ) - - cmd = cmd["Launching"][0] # type: ignore - - # check that it's a domain by default - assert "NODE_TYPE=domain" in cmd or "NODE_TYPE='domain'" in cmd - - # check that the node has a name - assert ( - "NODE_NAME=united_states_of_america" in cmd - or "NODE_NAME='united_states_of_america'" in cmd - ) - - # check that tail is on by default - assert " -d " not in cmd - - -def test_launch_with_longer_multiword_domain_name_with_preposition() -> None: - """This test is important because we want to make it convenient for users to launch nodes with - an arbitrary number of words.""" - - # COMMAND: "hagrid launch United Nations on docker" - args: list[str] = ["United", "Nations", "to", "docker"] - - verb = cli.get_launch_verb() - grammar = cli.parse_grammar(args=tuple(args), verb=verb) - verb.load_grammar(grammar=grammar) - cmd = cli.create_launch_cmd( - verb=verb, kwargs=defaultdict(lambda: None), ignore_docker_version_check=True - ) - - cmd = cmd["Launching"][0] # type: ignore - - # check that it's a domain by default - assert "NODE_TYPE=domain" in cmd or "NODE_TYPE='domain'" in cmd - - # check that the node has a name - assert "NODE_NAME=united_nations" in cmd or "NODE_NAME='united_nations'" in cmd - - # check that tail is on by default - assert " -d " not in cmd - - -def test_shortand_parse_of_multiword_name() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch Multiple Word Name Of Node' whenever they want to spin - up a new node with a name that has multiple words.""" - - # COMMAND: "hagrid launch" - args: tuple[str, ...] = ("United", "Nations") - args = grammar.launch_shorthand_support(args) - - # check that domain gets added to the end of the command - assert args == ( - "United Nations", - "domain", - ) - - -def test_shortand_parse_of_multiword_name_with_domain() -> None: - """This test is important because we want to make it convenient - for our users to just run 'hagrid launch Multiple Word Name Of Node' whenever they want to spin - up a new node with a name that has multiple words.""" - - # COMMAND: "hagrid launch" - args: tuple[str, ...] = ("United", "Nations", "domain") - args = grammar.launch_shorthand_support(args) - - # check that domain gets added to the end of the command - assert args == ( - "United Nations", - "domain", - ) From dbf65a09bbcb36c2f548bb1c0373995b997aa7fa Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:17:17 +0530 Subject: [PATCH 049/114] [lint] remove hagrid in pre commit config files --- .pre-commit-config.yaml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d243ca60d91..2b4c5fa22ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,7 +52,6 @@ repos: packages/syft/src/syft/proto.*| packages/syft/tests/syft/lib/python.*| packages/grid.*| - packages/hagrid.*| packages/syft/src/syft/federated/model_serialization/protos.py )$ @@ -95,31 +94,6 @@ repos: - id: ruff-format types_or: [python, pyi, jupyter] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 - hooks: - - id: mypy - name: "mypy: hagrid" - always_run: true - files: ^packages/hagrid - args: [ - "--ignore-missing-imports", - "--scripts-are-modules", - "--disallow-incomplete-defs", - "--no-implicit-optional", - "--warn-unused-ignores", - "--warn-redundant-casts", - "--strict-equality", - "--warn-unreachable", - # "--disallow-untyped-decorators", - "--disallow-untyped-defs", - "--disallow-untyped-calls", - "--namespace-packages", - "--install-types", - "--non-interactive", - "--config-file=tox.ini", - ] - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: @@ -206,7 +180,7 @@ repos: rev: "v3.0.0-alpha.9-for-vscode" hooks: - id: prettier - exclude: ^(packages/grid/helm|packages/grid/frontend/pnpm-lock.yaml|packages/hagrid/hagrid/manifest_template.yml|packages/syft/tests/mongomock) + exclude: ^(packages/grid/helm|packages/grid/frontend/pnpm-lock.yaml|packages/syft/tests/mongomock) # - repo: meta # hooks: From 6a5dbaddca1a2fa7b9ba72606b859fd338a1c93c Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:18:27 +0530 Subject: [PATCH 050/114] remove gitpod.yaml --- .gitpod.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 584f776b221..00000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,11 +0,0 @@ -tasks: - - init: pip install -e packages/hagrid - command: hagrid quickstart -ports: - - name: Jupyter - port: 8888 - visibility: public - - name: Nodes - port: 8081-8083 - onOpen: open-browser - visibility: public From faf038fbde4a428b9591b4044772a61a7f644274 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:20:26 +0530 Subject: [PATCH 051/114] remove hagrid references in .gitignore and bumpversion --- .bumpversion.cfg | 4 ---- .bumpversion_stable.cfg | 4 ---- .gitignore | 4 ---- 3 files changed, 12 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ad0c22faaf1..9d7d355d5b5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -50,8 +50,4 @@ first_value = 1 [bumpversion:file:packages/grid/helm/syft/values.yaml] -[bumpversion:file:packages/hagrid/hagrid/manifest_template.yml] - -[bumpversion:file:packages/hagrid/hagrid/deps.py] - [bumpversion:file:packages/syftcli/manifest.yml] diff --git a/.bumpversion_stable.cfg b/.bumpversion_stable.cfg index 806698f59f1..52b011ac7b1 100644 --- a/.bumpversion_stable.cfg +++ b/.bumpversion_stable.cfg @@ -13,7 +13,3 @@ serialize = {major}.{minor}.{patch} [bumpversion:file:packages/syft/src/syft/stable_version.py] - -[bumpversion:file:packages/hagrid/hagrid/stable_version.py] - -[bumpversion:file:packages/hagrid/hagrid/cache.py] diff --git a/.gitignore b/.gitignore index ae0b09e4342..fc3d10b8733 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,6 @@ build # docker compose volumes docker/data/* -# hagrid temps -packages/hagrid/syft -packages/hagrid/grid # vagrant .vagrant @@ -60,7 +57,6 @@ notebooks/**/*.pkl k3d-registry .envfile -packages/hagrid/.envfile # rendered template dir From ad4333bc4e879d08b24b867b4ee30c3473cff1d8 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 9 May 2024 08:27:01 +0530 Subject: [PATCH 052/114] remove unused scripts in scripts/ folder --- packages/grid/scripts/redeploy.sh | 49 -------- scripts/aa_demo/update_domain.sh | 29 ----- scripts/hagrid_hash | 1 - scripts/staging.json | 10 -- scripts/staging.py | 201 ------------------------------ 5 files changed, 290 deletions(-) delete mode 100755 packages/grid/scripts/redeploy.sh delete mode 100644 scripts/aa_demo/update_domain.sh delete mode 100644 scripts/hagrid_hash delete mode 100644 scripts/staging.json delete mode 100644 scripts/staging.py diff --git a/packages/grid/scripts/redeploy.sh b/packages/grid/scripts/redeploy.sh deleted file mode 100755 index 121f11c62f3..00000000000 --- a/packages/grid/scripts/redeploy.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# only run one redeploy.sh at a time -pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1 - -# cronjob logs: $ tail -f /var/log/syslog | grep -i cron - -# $1 is the PySyft dir -# $2 is the git repo like: https://github.com/OpenMined/PySyft -# $3 is the branch like: dev -# $4 is the permission user like: om -# $5 is the permission group like: om -# $6 is the node type like: domain -# $7 is the node name like: node -# $8 is the build directory where we copy the source so we dont trigger hot reloading -# $9 is a bool for enabling tls or not, where true is tls enabled -# $10 is the path to tls certs if available -# $11 release mode, production or development with hot reloading -# $12 docker_tag if set to local, normal local build occurs, otherwise use dockerhub - -if [[ ${11} = "development" ]]; then - RELEASE=development -else - RELEASE=production -fi - -if [[ -z "${12}" ]]; then - DOCKER_TAG="local" -else - DOCKER_TAG="${12}" -fi - -echo "Code has changed so redeploying with HAGrid" -rm -rf ${8} -cp -r ${1} ${8} -chown -R ${4}:${5} ${8} -/usr/sbin/runuser -l ${4} -c "pip install -e ${8}/packages/hagrid" -# /usr/sbin/runuser -l ${4} -c "hagrid launch ${7} ${6} to localhost --repo=${2} --branch=${3} --ansible-extras='docker_volume_destroy=true'" -if [[ "${9}" = "true" ]]; then - echo "Starting Grid with TLS" - HAGRID_CMD="hagrid launch ${7} ${6} to localhost --repo=${2} --branch=${3} --release=${RELEASE} --tag=${DOCKER_TAG} --tls --cert-store-path=${10}" - echo $HAGRID_CMD - /usr/sbin/runuser -l ${4} -c "$HAGRID_CMD" -else - echo "Starting Grid without TLS" - HAGRID_CMD="hagrid launch ${7} ${6} to localhost --repo=${2} --branch=${3} --release=${RELEASE} --tag=${DOCKER_TAG}" - echo $HAGRID_CMD - /usr/sbin/runuser -l ${4} -c "$HAGRID_CMD" -fi diff --git a/scripts/aa_demo/update_domain.sh b/scripts/aa_demo/update_domain.sh deleted file mode 100644 index 6efa4106b23..00000000000 --- a/scripts/aa_demo/update_domain.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# $1 domain ip -# $2 dataset url -# install syft in dev mode -pip install -U -e packages/syft - -# get domain name -NODE_NAME="$(sudo docker ps --format '{{.Names}}' | grep "celery" |rev | cut -c 16- | rev)" -echo "Domain Name: ${NODE_NAME}" -echo "Nuking Domain .. >:)"; - -# destroy current domain -hagrid land all -echo "Launching Domain .. hahaha >:)"; - -# re-launch domain -hagrid launch ${NODE_NAME} to docker:80 --dev --build-src="model_training_tests" - -# wait for domain to be up -hagrid check --timeout=120 - -echo "Domain lauch succeeded." -echo "Starting to upload dataset" - -# upload dataset -python scripts/aa_demo/upload_dataset.py $1 $2 - -echo "Upload dataset script complete." diff --git a/scripts/hagrid_hash b/scripts/hagrid_hash deleted file mode 100644 index 75aa7249f7f..00000000000 --- a/scripts/hagrid_hash +++ /dev/null @@ -1 +0,0 @@ -bcc1cc0354932a7d2cf4f35f781fde47 diff --git a/scripts/staging.json b/scripts/staging.json deleted file mode 100644 index f3b648cff6f..00000000000 --- a/scripts/staging.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "domain-staging-dev": { - "name": "domain-staging-dev", - "node_type": "domain", - "ip": "40.78.86.246", - "metadata_endpoint": "/api/v1/new/metadata", - "branch": "dev", - "commit_hash": "6bd3bea303107f8eef9ad0e43c85006161835fc9" - } -} diff --git a/scripts/staging.py b/scripts/staging.py deleted file mode 100644 index e158c72b30d..00000000000 --- a/scripts/staging.py +++ /dev/null @@ -1,201 +0,0 @@ -# stdlib -import json -import os -import subprocess -from typing import Any - -# third party -import git -import requests - -DEV_MODE = False -KEY = None -JSON_DATA = os.path.dirname(__file__) + "/staging.json" - - -def run_hagrid(node: dict) -> int: - name = node["name"] - node_type = node["node_type"] - ip = node["ip"] - branch = node["branch"] - cmd = ( - f"hagrid launch {name} {node_type} to {ip} --username=azureuser --auth-type=key " - f"--key-path={KEY} --repo=OpenMined/PySyft --branch={branch} --verbose" - ) - watch_shell(cmd) - - -def watch_shell(command: str) -> None: - process = subprocess.Popen( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) # nosec - while True: - output = process.stdout.readline().decode() - if output == "" and process.poll() is not None: - break - if output: - print(output.strip()) - rc = process.poll() - return rc - - -def shell(command: str) -> str: - try: - output = subprocess.check_output( # nosec - command, shell=True, stderr=subprocess.STDOUT - ) - except Exception: - output = b"" - return output.decode("utf-8").strip() - - -def metadata_url(node: dict) -> str: - ip = node["ip"] - endpoint = node["metadata_endpoint"] - return f"http://{ip}{endpoint}" - - -def check_metadata(node: dict) -> dict | None: - try: - res = requests.get(metadata_url(node)) - if res.status_code != 200: - print(f"Got status_code: {res.status_code}") - metadata = res.json() - name = node["name"] - print(f"{name} syft_version: ", metadata["syft_version"]) - return metadata - except Exception as e: - print(f"Failed to get metadata. {e}") - return None - - -def process_node(node: dict[str, Any]) -> tuple[bool, str]: - repo_hash = get_repo_checkout(node) - metadata = check_metadata(node) - hash_string = check_remote_hash(node) - redeploy = False - if metadata is None or hash_string is None: - print(f"redeploy because metadata: {metadata} and remote hash: {hash_string}") - redeploy = True - - if hash_string is not None and repo_hash != hash_string: - print("repo_hash", len(repo_hash), type(repo_hash)) - print("hash_string", len(hash_string), type(hash_string)) - print( - f"redeploy because repo_hash: {repo_hash} != remote hash_string: {hash_string}" - ) - redeploy = True - - if redeploy: - print("πŸ”§ Redeploying with HAGrid") - run_hagrid(node) - - hash_string = check_remote_hash(node) - if hash_string is None: - print(f"Cant get hash: {hash_string}") - - if hash_string is not None and hash_string != repo_hash: - print( - f"Hash doesnt match repo_hash: {repo_hash} != remote hash_string {hash_string}" - ) - - metadata = check_metadata(node) - if metadata is None: - print(f"Cant get metadata: {metadata}") - - if metadata and hash_string == repo_hash: - return True, repo_hash - return False, repo_hash - - -def get_repo_checkout(node: dict) -> str: - try: - branch = node["branch"] - repo_path = f"/tmp/{branch}/PySyft" - if not os.path.exists(repo_path): - os.makedirs(repo_path, exist_ok=True) - repo = git.Repo.clone_from( - "https://github.com/OpenMined/pysyft", - repo_path, - single_branch=True, - b=branch, - ) - else: - repo = git.Repo(repo_path) - if repo.is_dirty(): - repo.git.reset("--hard") - repo.git.checkout(branch) - repo.remotes.origin.pull() - sha = repo.head.commit.hexsha - return sha - except Exception as e: - print(f"Failed to get branch HEAD commit hash. {e}") - raise e - - -def run_remote_shell(node: dict, cmd: str) -> str | None: - try: - ip = node["ip"] - ssh_cmd = ( - f"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -i {KEY} azureuser@{ip}" - ) - shell_cmd = f'{ssh_cmd} "{cmd}"' - print("Running:", shell_cmd) - return shell(shell_cmd) - except Exception: - print("Failed to run ssh command: {}") - return None - - -def check_remote_hash(node: dict) -> str | None: - cmd = "sudo runuser -l om -c 'cd /home/om/PySyft && git rev-parse HEAD'" - return run_remote_shell(node, cmd) - - -def check_staging() -> None: - nodes = load_staging_data(JSON_DATA) - for name, node in nodes.items(): - print(f"Processing {name}") - good = False - try: - good, updated_hash = process_node(node=node) - node["commit_hash"] = updated_hash - nodes[name] = node - save_staging_data(JSON_DATA, nodes) - except Exception as e: - print(f"Failed to process node: {name}. {e}") - emoji = "βœ…" if good else "❌" - print(f"{emoji} Node {name}") - - -def load_staging_data(path: str) -> dict[str, dict]: - with open(path) as f: - return json.loads(f.read()) - - -def save_staging_data(path: str, data: dict[str, dict]) -> None: - print("Saving changes to file", path) - with open(path, "w") as f: - f.write(f"{json.dumps(data)}") - - -if __name__ == "__main__": - # stdlib - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument("--dev", action="store_true", help="Dev Mode") - parser.add_argument("--private-key", help="Dev Mode") - - args = parser.parse_args() - if args.dev: - DEV_MODE = True - if args.private_key: - path = os.path.expanduser(args.private_key) - if os.path.exists(path): - KEY = path - if KEY is None: - raise Exception("--private-key required") - print("DEV MODE", DEV_MODE) - - check_staging() From 109cabeabb817ec94ebb1d27e9e3746e2451511d Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 9 May 2024 13:19:33 +0800 Subject: [PATCH 053/114] Fix lint --- packages/syft/src/syft/client/api.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index ae16edcb285..ac486c11dc0 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -68,20 +68,17 @@ from ..service.job.job_stash import Job -IPYNB_BACKGROUND_METHODS = set( - [ - "getdoc", - "_partialmethod", - "__name__", - "__code__", - "__wrapped__", - "__custom_documentations__", - "__signature__", - "__defaults__", - "__kwdefaults__", - "__custom_documentations__", - ] -) +IPYNB_BACKGROUND_METHODS = { + "getdoc", + "_partialmethod", + "__name__", + "__code__", + "__wrapped__", + "__custom_documentations__", + "__signature__", + "__defaults__", + "__kwdefaults__", +} IPYNB_BACKGROUND_PREFIXES = ["_ipy", "_repr", "__ipython", "__pydantic"] From 131d64cf0f48974be24fa51db238485645728e07 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Thu, 9 May 2024 14:18:36 +0530 Subject: [PATCH 054/114] Add notebook documentation for deploying PySyft on a local k8s cluster --- .../deploy/01-kubernetes-quickstart.ipynb | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb diff --git a/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb b/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb new file mode 100644 index 00000000000..5d87d5d2a87 --- /dev/null +++ b/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quick Start Guide: Deploying PySyft on a Local Kubernetes Cluster" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "## Introduction\n", + "Welcome to our quick start guide for deploying PySyft on a local Kubernetes cluster! PySyft is a powerful framework for privacy-preserving machine learning, and deploying it on Kubernetes allows an easy way to quickly try out the full PySyft stack on your own system. This guide will walk you through the process step by step." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "## Prerequisites\n", + "Before we begin, ensure you have the following prerequisites installed on your system:\n", + "1. [Docker](https://docs.docker.com/install/): Docker is required to create and manage containers.\n", + "2. [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): kubectl is the command-line tool for interacting with Kubernetes clusters.\n", + "3. [k3d](https://k3d.io/v5.6.3/#installation): k3d is used to create local Kubernetes clusters.\n", + "4. [Helm](https://helm.sh/docs/intro/install/): Helm is the package manager for Kubernetes, used to install and manage applications on Kubernetes clusters." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deployment Steps\n", + "\n", + "### 1. Create a Local Kubernetes Cluster\n", + "First, create a local Kubernetes cluster named \"syft\" using k3d:\n", + "```sh\n", + "k3d cluster create syft\n", + "```\n", + "\n", + "### 2. Add and Update Helm Repo for Syft\n", + "Add the Helm repository for PySyft and update it:\n", + "```sh\n", + "helm repo add openmined https://openmined.github.io/PySyft/helm\n", + "helm repo update openmined\n", + "```\n", + "\n", + "### 3. Search for Available Syft Versions\n", + "Explore available versions of PySyft using Helm:\n", + "```sh\n", + "helm search repo openmined/syft --versions --devel\n", + "```\n", + "\n", + "### 4. Set Your Preferred Syft Chart Version\n", + "Set the version of PySyft you want to install:\n", + "```sh\n", + "SYFT_VERSION=\"\"\n", + "```\n", + "\n", + "### 5. Provision Helm Charts\n", + "Install PySyft on the Kubernetes cluster with your preferred version:\n", + "```sh\n", + "helm install my-domain openmined/syft --version $SYFT_VERSION --namespace syft --create-namespace --set ingress.className=\"traefik\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next Steps\n", + "Congratulations! You have successfully deployed PySyft on your local Kubernetes cluster. Now, you can explore its capabilities and use cases through our API example notebooks:\n", + "\n", + "πŸ“ [API Example Notebooks](../../api)\n", + "- [00-load-data.ipynb](../../api/0.8/00-load-data.ipynb)\n", + "- [01-submit-code.ipynb](../../api/0.8/01-submit-code.ipynb)\n", + "- [02-review-code-and-approve.ipynb](../../api/0.8/02-review-code-and-approve.ipynb)\n", + "- [03-data-scientist-download-result.ipynb](../../api/0.8/03-data-scientist-download-result.ipynb)\n", + "- [04-jax-example.ipynb](../../api/0.8/04-jax-example.ipynb)\n", + "- [05-custom-policy.ipynb](../../api/0.8/05-custom-policy.ipynb)\n", + "- [06-multiple-code-requests.ipynb](../../api/0.8/06-multiple-code-requests.ipynb)\n", + "- [07-domain-register-control-flow.ipynb](../../api/0.8/07-domain-register-control-flow.ipynb)\n", + "- [08-code-version.ipynb](../../api/0.8/08-code-version.ipynb)\n", + "- [09-blob-storage.ipynb](../../api/0.8/09-blob-storage.ipynb)\n", + "- [10-container-images.ipynb](../../api/0.8/10-container-images.ipynb)\n", + "- [11-container-images-k8s.ipynb](../../api/0.8/11-container-images-k8s.ipynb)\n", + "\n", + "Feel free to explore these notebooks to get started with PySyft and unlock its full potential for privacy-preserving machine learning!" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 215c9cd6882593a92dac507cc9ee8c95de96ee16 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 9 May 2024 17:30:32 +0800 Subject: [PATCH 055/114] Only patch ipython autocompletion in ipython environment --- packages/syft/src/syft/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/syft/src/syft/__init__.py b/packages/syft/src/syft/__init__.py index 645daeb6b2b..1f6d1187ba3 100644 --- a/packages/syft/src/syft/__init__.py +++ b/packages/syft/src/syft/__init__.py @@ -109,11 +109,17 @@ pass # nosec -try: - # third party - from IPython.core.guarded_eval import EVALUATION_POLICIES +def _patch_ipython_autocompletion() -> None: + try: + # third party + from IPython.core.guarded_eval import EVALUATION_POLICIES + except ImportError: + return + + ipython = get_ipython() + if ipython is None: + return - ipython = get_ipython() # type: ignore ipython.Completer.evaluation = "limited" ipython.Completer.use_jedi = False policy = EVALUATION_POLICIES["limited"] @@ -139,8 +145,8 @@ def patched_can_get_attr(value: Any, attr: str) -> bool: # this allows property getters to be used in nested autocomplete policy.can_get_attr = patched_can_get_attr -except Exception as e: - print(e) + +_patch_ipython_autocompletion() def module_property(func: Any) -> Callable: From 7ed0e5815fd9575c9d3523c548dec0602d20e1cc Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 9 May 2024 17:36:39 +0800 Subject: [PATCH 056/114] Attempt to fix import error in CI --- packages/syft/src/syft/client/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index ac486c11dc0..3bc6c846f50 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -53,11 +53,11 @@ from ..types.syft_object import SyftBaseObject from ..types.syft_object import SyftMigrationRegistry from ..types.syft_object import SyftObject -from ..types.syft_object import list_dict_repr_html from ..types.uid import LineageID from ..types.uid import UID from ..util.autoreload import autoreload_enabled from ..util.markdown import as_markdown_python_code +from ..util.table import list_dict_repr_html from ..util.telemetry import instrument from ..util.util import prompt_warning_message from .connection import NodeConnection From 5a895d0b3632a73ee82e236a034b717cd9fcd066 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Thu, 9 May 2024 16:05:09 +0530 Subject: [PATCH 057/114] Add port forward for k3d cluster --- notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb b/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb index 5d87d5d2a87..83b4a41ddbd 100644 --- a/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb +++ b/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb @@ -44,7 +44,7 @@ "### 1. Create a Local Kubernetes Cluster\n", "First, create a local Kubernetes cluster named \"syft\" using k3d:\n", "```sh\n", - "k3d cluster create syft\n", + "k3d cluster create syft -p \"8080:80@loadbalancer\"\n", "```\n", "\n", "### 2. Add and Update Helm Repo for Syft\n", @@ -69,7 +69,7 @@ "### 5. Provision Helm Charts\n", "Install PySyft on the Kubernetes cluster with your preferred version:\n", "```sh\n", - "helm install my-domain openmined/syft --version $SYFT_VERSION --namespace syft --create-namespace --set ingress.className=\"traefik\"\n", + "helm install my-syft openmined/syft --version $SYFT_VERSION --namespace syft --create-namespace --set ingress.className=\"traefik\"\n", "```" ] }, From 1d081642cddfebf6cee6aa2313653d165f659ae7 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Fri, 10 May 2024 02:19:47 +0530 Subject: [PATCH 058/114] Add placeholder notebooks and info about different deployment types --- .../deploy/00-deployment-types.ipynb | 99 +++++++++++++++++++ .../tutorials/deploy/01-deploy-python.ipynb | 18 ++++ .../deploy/02-deploy-container.ipynb | 18 ++++ ...ickstart.ipynb => 03-deploy-k8s-k3d.ipynb} | 2 +- .../deploy/04-deploy-k8s-azure.ipynb | 18 ++++ .../tutorials/deploy/05-deploy-k8s-gcp.ipynb | 18 ++++ .../tutorials/deploy/06-deploy-k8s-aws.ipynb | 18 ++++ .../tutorials/deploy/07-deploy-devspace.ipynb | 18 ++++ 8 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 notebooks/tutorials/deploy/00-deployment-types.ipynb create mode 100644 notebooks/tutorials/deploy/01-deploy-python.ipynb create mode 100644 notebooks/tutorials/deploy/02-deploy-container.ipynb rename notebooks/tutorials/deploy/{01-kubernetes-quickstart.ipynb => 03-deploy-k8s-k3d.ipynb} (98%) create mode 100644 notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb create mode 100644 notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb create mode 100644 notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb create mode 100644 notebooks/tutorials/deploy/07-deploy-devspace.ipynb diff --git a/notebooks/tutorials/deploy/00-deployment-types.ipynb b/notebooks/tutorials/deploy/00-deployment-types.ipynb new file mode 100644 index 00000000000..b9283a0c94c --- /dev/null +++ b/notebooks/tutorials/deploy/00-deployment-types.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction to PySyft Deployment Options\n", + "\n", + "PySyft offers various deployment options catering to different needs and environments. Each deployment option provides a unique set of advantages, allowing users to seamlessly integrate PySyft into their workflows, whether for local development, production deployment, or experimentation in cloud environments. Below, we explore the different deployment options supported by PySyft and provide insights into when each option is most suitable." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Local Python Deployment\n", + "\n", + "This deployment option runs PySyft locally within a Python environment. It is lightweight and runs everything in-memory, making it ideal for quick prototyping and testing.\n", + "\n", + "**Recommended For:** \n", + "- Development and testing on resource-constrained systems without Docker support.\n", + "- Rapid experimentation with PySyft APIs.\n", + "\n", + "Follow [01-deploy-python.ipynb](./01-deploy-python.ipynb) for deployment instructions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Single Container Deployment\n", + "\n", + "In this deployment, PySyft is encapsulated within a single Docker container, providing better isolation and portability compared to the local Python deployment.\n", + "\n", + "**Recommended For:**\n", + "- Resource-constrained systems with Docker support.\n", + "- Standardizing PySyft deployment across different environments.\n", + "\n", + "Follow [02-deploy-container.ipynb](./02-deploy-container.ipynb) for deployment instructions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Kubernetes Deployment\n", + "\n", + "This deployment option orchestrates the entire PySyft stack on a Kubernetes cluster, enabling scalable and efficient deployment in cloud or on-premises environments. Various Kubernetes configurations are available for deployment flexibility.\n", + "\n", + "**Recommended For:**\n", + "- Production-grade deployments requiring scalability and fault tolerance.\n", + "- Cloud-native environments where Kubernetes is the preferred orchestration tool.\n", + "\n", + " **[a. Local k3d Cluster Deployment](./03-deploy-k8s-k3d.ipynb)**\n", + " - Quick setup for local development and testing using a lightweight Kubernetes cluster.\n", + "\n", + " **[b. Azure Deployment](./04-deploy-k8s-azure.ipynb)**\n", + " - Deployment on Microsoft Azure cloud infrastructure for scalable and reliable operation.\n", + "\n", + " **[c. GCP Deployment](./05-deploy-k8s-gcp.ipynb)**\n", + " - Deployment on Google Cloud Platform for seamless integration with GCP services.\n", + "\n", + " **[d. AWS Deployment](./06-deploy-k8s-aws.ipynb)**\n", + " - Deployment on Amazon Web Services for robust and flexible cloud-based deployment." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Devspace Deployment\n", + "\n", + "This deployment option utilizes Devspace to streamline the development process for PySyft. It provides features such as local image building, port-forwarding, volume mounting, hot-reloading, and debugging to enhance the development experience.\n", + "\n", + "**Recommended For:**\n", + "- Developers contributing to PySyft codebase.\n", + "- Simplifying local development setup and debugging processes.\n", + "\n", + "Follow [07-deploy-devspace.ipynb](./07-deploy-devspace.ipynb) for deployment instructions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choosing the Right Deployment Option\n", + "\n", + "Selecting the appropriate deployment option depends on factors such as development objectives, resource constraints, scalability requirements, and familiarity with the deployment environment. For quick experimentation and local development, the local Python deployment or single container deployment may suffice. However, for production-grade deployments requiring scalability and reliability, Kubernetes deployment is recommended. Developers actively contributing to PySyft can benefit from the Devspace deployment option for a streamlined development experience." + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/01-deploy-python.ipynb b/notebooks/tutorials/deploy/01-deploy-python.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/01-deploy-python.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/02-deploy-container.ipynb b/notebooks/tutorials/deploy/02-deploy-container.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/02-deploy-container.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb similarity index 98% rename from notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb rename to notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb index 83b4a41ddbd..d1ee9ff67f9 100644 --- a/notebooks/tutorials/deploy/01-kubernetes-quickstart.ipynb +++ b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Quick Start Guide: Deploying PySyft on a Local Kubernetes Cluster" + "# Deploying PySyft on a Local Kubernetes Cluster" ] }, { diff --git a/notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb b/notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb b/notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb b/notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/tutorials/deploy/07-deploy-devspace.ipynb b/notebooks/tutorials/deploy/07-deploy-devspace.ipynb new file mode 100644 index 00000000000..71c158afddd --- /dev/null +++ b/notebooks/tutorials/deploy/07-deploy-devspace.ipynb @@ -0,0 +1,18 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "TODO" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From b0c9f20d5d1ac82b1deb0d12a97f128282c792d9 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 10 May 2024 09:12:27 +0530 Subject: [PATCH 059/114] delete pr stack tests public --- .github/workflows/pr-tests-stack-public.yml | 216 -------------------- 1 file changed, 216 deletions(-) delete mode 100644 .github/workflows/pr-tests-stack-public.yml diff --git a/.github/workflows/pr-tests-stack-public.yml b/.github/workflows/pr-tests-stack-public.yml deleted file mode 100644 index a036d7b5e07..00000000000 --- a/.github/workflows/pr-tests-stack-public.yml +++ /dev/null @@ -1,216 +0,0 @@ -name: PR Tests - Stack - Public - -on: - workflow_call: - - workflow_dispatch: - inputs: - none: - description: "Run Stack Integration Tests Manually" - required: false - -concurrency: - group: stackpublic-${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || github.workflow_ref }} - cancel-in-progress: true - -jobs: - pr-tests-stack-public: - strategy: - max-parallel: 99 - matrix: - os: [ubuntu-latest, macos-latest, windows] - python-version: ["3.12"] - pytest-modules: ["frontend network"] - fail-fast: false - - runs-on: ${{matrix.os}} - - steps: - - name: "clean .git/config" - if: matrix.os == 'windows' - continue-on-error: true - shell: bash - run: | - echo "deleting ${GITHUB_WORKSPACE}/.git/config" - rm ${GITHUB_WORKSPACE}/.git/config - - - uses: actions/checkout@v4 - - - name: Check for file changes - uses: dorny/paths-filter@v3 - id: changes - with: - base: ${{ github.ref }} - token: ${{ github.token }} - filters: .github/file-filters.yml - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - if: steps.changes.outputs.stack == 'true' - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - if: steps.changes.outputs.stack == 'true' - run: | - pip install --upgrade pip uv==0.1.35 - uv --version - - - name: Get pip cache dir - if: steps.changes.outputs.stack == 'true' - id: pip-cache - shell: bash - run: | - echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT - - - name: pip cache - uses: actions/cache@v4 - if: steps.changes.outputs.stack == 'true' - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-uv-py${{ matrix.python-version }} - restore-keys: | - ${{ runner.os }}-uv-py${{ matrix.python-version }} - - - name: Install tox - if: steps.changes.outputs.stack == 'true' - run: | - pip install --upgrade tox tox-uv==1.5.1 - - - name: Show choco installed packages - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' - uses: crazy-max/ghaction-chocolatey@v3 - with: - args: list --localonly - - - name: Install git - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' - uses: crazy-max/ghaction-chocolatey@v3 - with: - args: install git.install --params "/GitAndUnixToolsOnPath /WindowsTerminal /NoAutoCrlf" -y - - - name: Install cmake - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' - uses: crazy-max/ghaction-chocolatey@v3 - with: - args: install cmake.portable --installargs 'ADD_CMAKE_TO_PATH=System' -y - - - name: Check cmake version - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' - run: | - cmake --version - shell: cmd - - - name: Install visualcpp-build-tools - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' - uses: crazy-max/ghaction-chocolatey@v3 - with: - args: install visualstudio2019-workload-vctools -y - - - name: Install Docker Compose - if: steps.changes.outputs.stack == 'true' && runner.os == 'Linux' - shell: bash - run: | - mkdir -p ~/.docker/cli-plugins - DOCKER_COMPOSE_VERSION=v2.21.0 - curl -sSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose - chmod +x ~/.docker/cli-plugins/docker-compose - - - name: Docker on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-latest' - uses: crazy-max/ghaction-setup-docker@v3.1.0 - - - name: Docker Compose on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-latest' - shell: bash - run: | - brew install docker-compose - mkdir -p ~/.docker/cli-plugins - ln -sfn /usr/local/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose || true - docker compose version - - - name: Remove existing containers - if: steps.changes.outputs.stack == 'true' - continue-on-error: true - shell: bash - run: | - docker rm $(docker ps -aq) --force || true - docker volume prune -f || true - docker buildx use default || true - - # - name: Run integration tests - # if: steps.changes.outputs.stack == 'true' - # timeout-minutes: 60 - # env: - # HAGRID_ART: false - # PYTEST_MODULES: "${{ matrix.pytest-modules }}" - # HAGRID_FLAGS: "--tag=beta --test --build-src=dev" - # run: | - # tox -e stack.test.integration - - - name: Run integration tests - uses: nick-fields/retry@v3 - if: steps.changes.outputs.stack == 'true' - env: - HAGRID_ART: false - PYTEST_MODULES: "${{ matrix.pytest-modules }}" - HAGRID_FLAGS: "--tag=beta --test --build-src=dev" - with: - timeout_seconds: 1800 - max_attempts: 3 - command: tox -e stack.test.integration - continue-on-error: true - - - name: Reboot node - if: matrix.os == 'windows' && failure() - run: | - shutdown /r /t 1 - - - name: Run log collector - timeout-minutes: 5 - if: failure() - shell: bash - run: | - python ./scripts/container_log_collector.py - - - name: Get job name and url - id: job_name - if: failure() - shell: bash - run: | - echo "job_name=$(echo ${{ github.job }})" >> $GITHUB_OUTPUT - echo "url=$(echo ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_OUTPUT - - - name: Get current date - id: date - if: failure() - shell: bash - run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Upload logs to GitHub - uses: actions/upload-artifact@master - if: failure() - with: - name: ${{ matrix.os }}-${{ steps.job_name.outputs.job_name }}-${{ matrix.pytest-modules }}-logs-${{ steps.date.outputs.date }} - path: ./logs/${{ steps.job_name.outputs.job_name}}/ - - - name: Get pull request url - id: pull_request - if: failure() - shell: bash - run: | - echo "url=$(echo ${{ github.event.pull_request.html_url }})" >> $GITHUB_OUTPUT - - - name: Job Report Status - if: github.repository == 'OpenMined/PySyft' && failure() - uses: ravsamhq/notify-slack-action@v2 - with: - status: ${{ job.status }} - notify_when: "failure" - notification_title: " {workflow} has {status_message}" - message_format: "${{matrix.os}} {emoji} *{job}* {status_message} in {run_url}" - footer: "Find the PR here ${{ steps.pull_request.outputs.url }}" - mention_users: "U01LNCACY03,U8KUAD396,UNMQ2SJSW,U01SAESBJA0" - mention_users_when: "failure,warnings" - env: - SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK_WEBHOOK_URL }} From f583e0844dc5c0371f58f984e5bb5cfd9a6c57b1 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 10 May 2024 09:15:20 +0530 Subject: [PATCH 060/114] delete hagrid quick start --- notebooks/quickstart/00-quickstart.ipynb | 115 ----- notebooks/quickstart/01-install-wizard.ipynb | 500 ------------------- notebooks/quickstart/img/edit.png | Bin 3061 -> 0 bytes notebooks/quickstart/img/head.png | Bin 4411 -> 0 bytes notebooks/quickstart/img/run.png | Bin 3627 -> 0 bytes 5 files changed, 615 deletions(-) delete mode 100644 notebooks/quickstart/00-quickstart.ipynb delete mode 100644 notebooks/quickstart/01-install-wizard.ipynb delete mode 100644 notebooks/quickstart/img/edit.png delete mode 100644 notebooks/quickstart/img/head.png delete mode 100644 notebooks/quickstart/img/run.png diff --git a/notebooks/quickstart/00-quickstart.ipynb b/notebooks/quickstart/00-quickstart.ipynb deleted file mode 100644 index d5b14e2d8e0..00000000000 --- a/notebooks/quickstart/00-quickstart.ipynb +++ /dev/null @@ -1,115 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "9c0d3bd0-cd25-4794-81de-c413f260de3c", - "metadata": {}, - "source": [ - "# HAGrid Quickstart BETA" - ] - }, - { - "cell_type": "markdown", - "id": "b4813d9f-daec-4954-96aa-90c01159d396", - "metadata": {}, - "source": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " πŸ“š quickstart\n", - " \n", - " πŸ§™β€β™‚οΈ Install Wizard\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "a50d74d3-66f0-4181-8c2f-b07fdf6b0979", - "metadata": {}, - "source": [ - " " - ] - }, - { - "cell_type": "markdown", - "id": "df038714-df01-4c56-a84c-b66a42d6cd81", - "metadata": {}, - "source": [ - "
Step 1. Run quickstart
" - ] - }, - { - "cell_type": "markdown", - "id": "3e0dd95e-38f8-46d4-b75a-d1bc9c6ac103", - "metadata": {}, - "source": [ - "Simply `import` and run `quickstart` by clicking in the grey cell below πŸ‘‡πŸ½ and pressing `Shift` + `Return` on your keyboard, or use the `Run` menu at the top of the window." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c71be622-bb83-4d32-aeaa-00f2aca6ee80", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# third party\n", - "from hagrid import quickstart\n", - "\n", - "quickstart" - ] - }, - { - "cell_type": "markdown", - "id": "ec809a11-95c7-4783-a397-58c93eb19dcf", - "metadata": {}, - "source": [ - "
Step 2. Download a Tutorial
" - ] - }, - { - "cell_type": "markdown", - "id": "27a1c075-3082-408e-8486-ab9df41a8442", - "metadata": {}, - "source": [ - "Above you will see a list of available tutorials, simply add the name in `quotes` into the `quickstart.download` function and run the cell below just like before." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f5507b20-5149-4b6d-b7e0-0883a2358ceb", - "metadata": {}, - "outputs": [], - "source": [ - "# paste and run any commands here" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/quickstart/01-install-wizard.ipynb b/notebooks/quickstart/01-install-wizard.ipynb deleted file mode 100644 index 7050c8b5b8f..00000000000 --- a/notebooks/quickstart/01-install-wizard.ipynb +++ /dev/null @@ -1,500 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "5198b587-cf2f-4354-bdbb-08f9ecb46abf", - "metadata": {}, - "source": [ - "# HAGrid Install πŸ§™πŸ½β€β™‚οΈ Wizard BETA" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "a156040f-39cc-4660-a32a-5d2c2ff586b7", - "metadata": {}, - "source": [ - "\n", - " \n", - " \n", - " \n", - "
\n", - " πŸ“š quickstart / 01-install-wizard.ipynb\n", - "
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "cf271e52-b3aa-48f6-b85f-353b328dd463", - "metadata": {}, - "source": [ - "Welcome to the HAGrid Quickstart Install Wizard. \n", - "There are several different components required to use `Syft`. To make setup easy this wizard will automatically detect your current system configuration and make recommendations on what you need to complete setup.\n", - "\n", - "Run each step by clicking in the grey cell below πŸ‘‡πŸ½ and pressing `Shift` + `Return` on your keyboard, or use the `Run` menu at the top of the window.\n", - "\n", - "**How the Install Wizard Works** \n", - "At each step the πŸ§™πŸ½β€β™‚οΈ Wizard will try to find various software and packages on your machine. \n", - "If you see an item marked with a ❌ red cross and the message `🚨 Some issues were found` it should include a description of the issue, a solution and optionally a way to resolve the solution directly by running a command. These commands can br Copy + Pasted into a cell and ran here, or if you know how to use the terminal on your computer simply remove the `!` at the start of the command and paste it there instead. After you have resolved the issue you can run the step again to verify it is fixed with a βœ… green tick." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d657c28d-5388-45cc-ba37-f5e6401c0c88", - "metadata": {}, - "source": [ - "
Step 1. Import Install πŸ§™πŸ½β€β™‚οΈ Wizard BETA
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad558ade-ee9b-4c61-8cbd-68eadd2f07fa", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# third party\n", - "import hagrid\n", - "from hagrid import wizard" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5568b6bd-0015-458a-bb33-be9a927d0ffa", - "metadata": {}, - "source": [ - "
Step 2. HAGrid Updates
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f5ab8626-0da1-4a8e-be94-8871724da34c", - "metadata": {}, - "source": [ - "It's a good idea to keep `HAGrid` updated as we push out fixes and features very frequently. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a546b80e-ec38-4662-9f6b-0caa5b581bdc", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "wizard.check_hagrid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8060ab70-f5e1-4a09-bcb5-0646c84c478c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# paste and run any commands here" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "871d2ad4-77ff-4381-a26c-036717d948b8", - "metadata": {}, - "source": [ - "
Step 3. Installing PySyft
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1998c1a5-2fb4-4730-98e1-a4e2c360c5b7", - "metadata": {}, - "source": [ - "`PySyft` is a python library which requires Python 3.9+." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b954a90e-ad48-417f-bbe7-cd2d75cecc5e", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "wizard.check_syft" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "554dfac3-9022-44f2-8c32-30af1b3a8dd8", - "metadata": {}, - "outputs": [], - "source": [ - "# paste and run any commands here" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "3d82f627-6fb6-4f03-a0c4-2ef68c6bf7f9", - "metadata": { - "tags": [] - }, - "source": [ - "
Step 4. Python Server
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7278fdb2-f1ba-4f12-a10b-50a43270c59f", - "metadata": { - "tags": [] - }, - "source": [ - "To do the `quickstart` tutorials, you can just run a basic `PyGrid` domain directly in python.\n", - "\n", - "You can do this either from `jupyter` / `python` or from the command line." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8b205fc3-c84e-4638-82cf-0bfa25b206b5", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# syft absolute\n", - "import syft as sy\n", - "\n", - "sy.requires(\">=0.8.2.b0\")\n", - "node = sy.orchestra.launch(name=\"test-domain-1\", port=8080, dev_mode=True, reset=True)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "aa136f0a-debc-4219-858b-1814ed3cad46", - "metadata": { - "tags": [] - }, - "source": [ - "We can now log into the node with the default credentials." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4dd63cc9-5fe1-42af-9863-65a74eb2fc28", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "domain = sy.login(email=\"info@openmined.org\", password=\"changethis\", port=8080)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5259b7f2-b480-4346-9cc7-86305fef76b5", - "metadata": {}, - "source": [ - "Let's see whats available on the API." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "312d1e50-99c4-4120-9dfc-caa56cee62fd", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "domain.api" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cda9bf88-4273-48aa-b656-005a8f456e6c", - "metadata": {}, - "outputs": [], - "source": [ - "# paste and run any commands here" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "956202c6-1d24-4889-aeda-6a7efe4a4055", - "metadata": {}, - "source": [ - "Okay, now let's shutdown the server." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb2a0096-6c15-4ac9-9446-6532c1524381", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "node.land()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7b2f1e6c-e469-4be5-b771-3724f92d2305", - "metadata": {}, - "source": [ - "πŸ‘ˆπŸΏ Click here to go back to Quickstart Home" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "efc6ebc7-43e6-4612-bf44-7ce14e488d01", - "metadata": {}, - "source": [ - "
\n", - "
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "e05d1893-3347-41a3-8ee3-c04fd8f12d0f", - "metadata": {}, - "source": [ - "
Step 5. Docker Setup (Optional)
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "2fa85963-4635-4ce3-8be6-36b308aa26d5", - "metadata": {}, - "source": [ - "`PyGrid` can also run as a set of containerized services on a container host. Let's ask `hagrid` to check if we have all the right dependencies installed. If we don't it will make some recommendations on what to install." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "83f58a56-7c5e-4dfb-94c6-0fd1ce950a3f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# third party\n", - "from hagrid import wizard\n", - "\n", - "wizard.check_docker" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "98b47e48-582f-4261-838b-1b7b2844f945", - "metadata": {}, - "outputs": [], - "source": [ - "# paste and run any commands here" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "ee41f9c5-8014-41e6-b9a1-542694cf2d31", - "metadata": {}, - "source": [ - "
Step 6. Start a Test Domain
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "068440ad-f0a0-4a71-bcca-52e598bf1968", - "metadata": {}, - "source": [ - "You are now ready to start a `domain` on your local machine with 🐳 Docker. Simply run the next cell and wait until it is completed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f06b0bf-c8c1-4b3c-87c8-b0f0809fcbfc", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# syft absolute\n", - "import syft as sy\n", - "\n", - "sy.requires(\">=0.8.2.b0\")\n", - "node = sy.orchestra.launch(\n", - " name=\"test-domain-1\", node_type=\"domain\", port=8081, tag=\"beta\", verbose=True\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7d5c9892-1b70-4a68-b41d-8c9c06c19df0", - "metadata": {}, - "source": [ - "
Step 7. Check Domain Health
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "b7846431-69b1-46bb-a611-e2ce47eb277f", - "metadata": {}, - "source": [ - "To ensure our domain has finished starting we can ask `hagrid` to check its health for us. Run the below cell to check your `domain` on localhost. You can also visit the links to see the `UI` and `api` endpoints in your browser." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "71c1fd7d-fb7b-43dc-9608-053a6313b1b4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "hagrid.check(\"localhost:8081\", timeout=120)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "91148fc9-a26f-4964-bb17-efd5a26f465d", - "metadata": {}, - "source": [ - "
Step 8. Domain Login
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "07093a4b-d463-4fe5-9861-09e7d32b63e0", - "metadata": {}, - "source": [ - "We now log into the Domain Node using the default admin username and password." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01126584-3449-4c4b-9fe4-1c727a7a0ee3", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# syft absolute\n", - "import syft as sy\n", - "\n", - "sy.requires(\">=0.8.2.b0\")\n", - "domain = sy.login(email=\"info@openmined.org\", password=\"changethis\", port=8081)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d4d7ccbb-0b48-41cf-bf7c-9b41f77924d0", - "metadata": {}, - "source": [ - "
Step 9. Shutdown Domain
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "8e3fc2ee-5247-4fa1-9e31-9d9464765e0d", - "metadata": {}, - "source": [ - "If your domain started correctly you are now done with the Install Wizard and ready to do some tutorials. We can shutdown this domain by running the `hagrid` land command in the below cell. If you are done now you can go ahead and shutdown your domain, or if you would prefer to keep it running skip this step." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0d12f71-26eb-4317-a8ea-7e45579b7b59", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "node.land()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "c288eae3-6109-4b26-ac68-7ab772518919", - "metadata": {}, - "source": [ - "
βœ… Install πŸ§™πŸ½β€β™‚οΈ Wizard Complete
" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6663f1cc-648c-43e0-aca8-4d55039e8a6d", - "metadata": {}, - "source": [ - "πŸ‘ˆπŸΏ Click here to go back to Quickstart Home" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.1" - }, - "vscode": { - "interpreter": { - "hash": "1e7e90b573593ba97b24c163dae9a6c9173808a1bc968e87367841cbed28165e" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/quickstart/img/edit.png b/notebooks/quickstart/img/edit.png deleted file mode 100644 index 3ceaaf9438df46cd52b510e638eb0d6b5bd11587..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3061 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

d<3ITe@g~!9tD7it2_0E=6`MzRt{*(4M=S; zn9-{P5@RAoS)0HdiDA%g-m`?0nef^hHZC5-#0iDb|Yn+?^}l6vX*%#r9-q#Eg4 z^Cw(7up3Ib{N+Kre)ka^Ts#;WuAVuGkuB><0<2Gs?_sfFhTi^+>(GTg{}c=^6I(Y& zxWA9#(C81npk2H35VlTk@bV4BLN#L<1%1GdMCFUXk1D$v;<8;5tHEtm>+hIZHT2}5IH*Yd@j4;kF=Uoof??2oKP;HBM& zxxHU!cwD32Tw{X%wS`;V1xfDDIKO8*R7#~-eF8QqSveJUdl%Dm-FgI=Z${Anw(#HBXEc!7;3cm}8b&kyp7yrvtMmy1 z4sLF4@0U5TQrZz26U%uTC%ABN_hM+Ud2EC6H2c)Uz#(eVt{Lv{T*APL>FS1A7*y|9 za`Xg&>QmF!jj2o9lybZ3bl*3nzAr6?CSMk$DWk-$>sVm$6kc>}JOQ)RRs{^r+1U;A zM+`7{GG%1bYPh*+wji|gE6pgK&~Gu#fO?hSV)cO6%8B)7=>Tf#G+A7l8OO#a;s({8 zY;8{*oZ(5(+&sLnckK%Fs`kBMWmNU=aDC-a@?*>4^S`xlJ-r@ICkmkLH65;*Jr^zR zq`=~lHABk>uDPA7lWbor41CPCv<}Buo=?M%tZF(*^u9zS7^wF zoSTY8JsMHNQJjzCd0F$_TMM%2D`&R|3f!yA(QYk!YhEBZoVxd2MCG$q|C?z84=!KrN)(GWJqf-VivQzW?Ye>X+o zQ}>dFWeR|*y6`Gm#3z*$6moOruyJx@Xt++nfY)_spjkb91mmu4OI#W?vfeM4^G{tY z?a&BIx;Do2HnlOL;V(373J;+<9qX|r74~TaiB#x51{ih83|mJ0iN4i!&*6AmX-_9F>T`5^%FtfqYA#x6u5|yhiKC0HwdHortiS8xrcp11t)GbG z1E$EjJ(rz59&a6r!^Z8gw09HC=~NF}M*a!cePam7LI$IgVy>8B=eWPoPek&&deUg* z=HwKGX2ZT+A7+|i>MAr$l`1t{J$(wGk*sK_a@xVr_;&yL)rI_8pQ_(rN!v#7TsjKz zTc<*{%b0xBoI+M3w_1o4i&;p#F$0;1#f??wcWgQ5yP6*eqmwXU{$B4@Le$m3EPa2xZMaT2hE^- zq_HCD#GqxAj%*wK7y6Wu$N9Uv2mvz#@J_jGU1SA@gmLZ3&?x<|)utUR2YW{tcWcW& z3^V_#4VOj385Rq&ohHaQJF}3LNv*hR)39;a?^HSorz1AEt_wxW%K$zs*G;k<3@mM( z3KHCh*4ng#Oo0~KZWH8On=2B8_@NqRZiJ=18W-yEaWlc+ z%~@zKlq>tO3fYdWRH~?_W{F!Qo;cP(W50Ip-~p`Y+#I3g^cj{G+8z^58zcrL49?@w zSZW*n%8{8DP4lK%p-34{YEG81$ek& zFt*Q_fY^v|={Nq10C$kazpCg7B&VbzBrFnIYjgKNz{#x)s<@@y6|^+zWw#tT-sU_f zP}08jrWN)pdjxGYeW*gXmX@#MYPs_eCr~H9=>_MJ9LkLzr z$zIZp{vyD2)pUD{N}*8la(VdMS|V=W{36z7jI`s^p?bWq{QQ~B3u>iY2-tJd4)GiO zrG;0Qtm`)CI70B`xw%Mly3e4BSzFF^il8KnF68;0MP9b7Bv33heX*Bm-m>mn zui8QDZuut-ElK%h{pg%LDiCS3UL)nm3<9?hYTG4c0HVk3mKMl)Bk~elk*C%;_fnNf z)-~!?|9QiYUi9PaNhnh)y71ggsB==FP6;YdasbpxzAR|(%Swcrow`aVP%Ie-E4f_$ zYvDajuQt5T2wp!r@tZU>V);TUKuVG;PlV7=togeC#_hKRa1ve215%PEXh@)ZQZ~J3 z4BejNX#OVtK=6CKDT{y5mw(uoEYMf8T1kxP^)hqj^+z^nA9vcIo5k0RufUjS}%l*IO{{d-7T)?zL#LNHy002ovPDHLkV1j?9 BreFX7 diff --git a/notebooks/quickstart/img/run.png b/notebooks/quickstart/img/run.png deleted file mode 100644 index c3a678a9fd105eb7e93f0bb30f60e87734b38693..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3627 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 From c349571468ce7d432dc916055f430a4238941bce Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 10 May 2024 09:18:15 +0530 Subject: [PATCH 061/114] remove hagrid related notebooks in notebooks folder --- .../01-setting-up-dev-mode.ipynb | 1 - .../tutorials/data-engineer/03-hagrid.ipynb | 73 ------------------- .../model-auditing/colab/01-user-log.ipynb | 2 +- 3 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 notebooks/tutorials/data-engineer/03-hagrid.ipynb diff --git a/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb b/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb index ed84817235f..3b06a289289 100644 --- a/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb +++ b/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb @@ -110,7 +110,6 @@ "```\n", "> tox -l\n", "\n", - "hagrid.publish\n", "lint\n", "stack.test.integration\n", "syft.docs\n", diff --git a/notebooks/tutorials/data-engineer/03-hagrid.ipynb b/notebooks/tutorials/data-engineer/03-hagrid.ipynb deleted file mode 100644 index 3ad7cf9c25d..00000000000 --- a/notebooks/tutorials/data-engineer/03-hagrid.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Installing HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Python PATH" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Debugging HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "## Ansible and Windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb b/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb index f53c1374203..0a9878ba9b3 100644 --- a/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb +++ b/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb @@ -110,7 +110,7 @@ " \n", "**C) From the command line (supports docker/kubernetes)**\n", " - setup for production\n", - " - run `syft launch` or `hagrid launch` from the terminal\n", + " - run `syft launch` from the terminal\n", " \n", " \n", "We are using the **A)** here, as it is the only option available using google colab, switching to a real webserver is as easy as running this notebook in jupyter locally and adding a port. Read more about deployment on our [README.md](https://github.com/OpenMined/PySyft) and other setups for syft [here](https://github.com/OpenMined/PySyft/tree/dev/notebooks/tutorials/data-engineer)" From b7c554be4ea2e5882096bb81f34c39191d6b4066 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 10 May 2024 09:22:11 +0530 Subject: [PATCH 062/114] remove unused scripts --- packages/grid/scripts/cron.sh | 104 ---------------------------------- 1 file changed, 104 deletions(-) delete mode 100755 packages/grid/scripts/cron.sh diff --git a/packages/grid/scripts/cron.sh b/packages/grid/scripts/cron.sh deleted file mode 100755 index 9b0bfe530a6..00000000000 --- a/packages/grid/scripts/cron.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash - -# cronjob logs: $ tail -f /var/log/syslog | grep -i cron - -# $1 is the PySyft dir -# $2 is the git repo like: https://github.com/OpenMined/PySyft -# $3 is the branch like: dev -# $4 is the permission user like: om -# $5 is the permission group like: om -# $6 is the node type like: domain -# $7 is the node name like: node -# $8 is the build directory where we copy the source so we dont trigger hot reloading -# $9 is a bool for enabling tls or not, where true is tls enabled -# $10 is the path to tls certs if available -# $11 release mode, production or development with hot reloading -# $12 docker_tag if set to local, normal local build occurs, otherwise use dockerhub - -# these commands cant be used because they trigger hot reloading -# however without them accidental changes to the working tree might cause issues -# with the fetch process so we should consider changing how this works perhaps by -# copying the code into a folder for execution and keeping the git repo seperate - -# git checkout main --force -# git branch -D $3 || true -# git checkout $3 --force - -pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1 - -cd $1 -START_HASH=$(git rev-parse HEAD) -CURRENT_REMOTE=$(git remote -v | head -n 1 | cut -d ' ' -f 1 | awk '{print $2}') -CURRENT_BRANCH=$(git branch --show-current) -echo "Running autoupdate CRON" - -# does https://github.com/OpenMined/PySyft contain OpenMined/PySyft -if [[ ! "$CURRENT_REMOTE" == *"$2"* ]] -then - echo "Switching remotes to: ${2}" - git remote rm origin || true - git remote add origin https://github.com/$2 - git fetch origin - echo "Checking out branch: ${3}" - git reset --hard "origin/${3}" - git checkout "origin/${3}" --force - chown -R $4:$5 . -fi - -if [ "$CURRENT_BRANCH" != "$3" ] -then - echo "Checking out branch: ${3}" -fi - -git fetch origin -git reset --hard "origin/${3}" -git checkout "origin/${3}" --force -chown -R $4:$5 . - -END_HASH=$(git rev-parse HEAD) -CONTAINER_VERSION=$(docker ps --format "{{.Names}}" | grep 'backend' | head -1l | xargs -I {} docker exec {} env | grep ^VERSION= | sed 's/VERSION=//') -CONTAINER_HASH=$(docker ps --format "{{.Names}}" | grep 'backend' | head -1l | xargs -I {} docker exec {} env | grep VERSION_HASH | sed 's/VERSION_HASH=//') - -REDEPLOY="0" - - -# see hagrid --release options -if [[ ${11} = "development" ]]; then - RELEASE=development -else - RELEASE=production -fi - -if [[ -z "${12}" ]]; then - DOCKER_TAG="local" -else - DOCKER_TAG="${12}" -fi - -if [[ "$CONTAINER_HASH" == "dockerhub" ]] -then - echo "Version: $CONTAINER_VERSION from dockerhub deployed" -elif [[ "$START_HASH" != "$END_HASH" ]] -then - echo "Git hashes $START_HASH vs $END_HASH dont match, redeploying" - REDEPLOY="1" -elif [[ ! "$END_HASH" == *"$CONTAINER_HASH"* ]] -then - echo "Container hash $END_HASH not in $CONTAINER_HASH, redeploying" - REDEPLOY="1" -elif [[ -z "$CONTAINER_HASH" ]] -then - echo "Container hash $CONTAINER_HASH is not valid, redeploying" - REDEPLOY="1" -fi - -echo "START_HASH=$START_HASH" -echo "END_HASH=$END_HASH" -echo "CONTAINER_HASH=$CONTAINER_HASH" -echo "REDEPLOY=$REDEPLOY" - -if [[ ${REDEPLOY} != "0" ]]; then - bash /home/om/PySyft/packages/grid/scripts/redeploy.sh $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${RELEASE} ${DOCKER_TAG} -fi - -echo "Finished autoupdate CRON" From f8d3071fca226e0351e794aa2dc71ec26c2466e9 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Sun, 12 May 2024 22:54:35 +0530 Subject: [PATCH 063/114] Add loc`al in-memory python deployment tutorial --- .../tutorials/deploy/01-deploy-python.ipynb | 182 +++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/notebooks/tutorials/deploy/01-deploy-python.ipynb b/notebooks/tutorials/deploy/01-deploy-python.ipynb index 71c158afddd..76dbc18f7c1 100644 --- a/notebooks/tutorials/deploy/01-deploy-python.ipynb +++ b/notebooks/tutorials/deploy/01-deploy-python.ipynb @@ -4,13 +4,191 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "TODO" + "# Local in-memory python deployment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "One of the quickest way to try out PySyft is to install the pre-built python package on your local environment using pip. The python package is lightweight and runs the PySyft stack in-memory.\n", + "\n", + "**Recommended For:**\n", + "- Development and testing on resource-constrained systems without Docker support.\n", + "- Rapid experimentation with PySyft APIs." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "Before we begin, ensure you have the following prerequisites installed on your system:\n", + "1. Python 3.10 - 3.12\n", + "2. pip (or uv)\n", + "3. venv (optional, but recommended)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deployment Steps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installing Syft\n", + "1. Create and activate a python virtual environment (Optional, but recommended)\n", + " ```bash\n", + " python -m venv venv/\n", + " source venv/bin/activate\n", + " ```\n", + "\n", + "2. Install PySyft\n", + " ```bash\n", + " pip install syft\n", + " ```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Working with Python Domain\n", + "\n", + "`PySyft` makes it very easy to develop against a domain in a notebook by providing an interface (`sy.orchestra`) that allows you to start a domain with a webserver in a notebook in the background, which is a lightweight version of a Domain that would be used in production. You can specify options such as what kind of database you are using, whether you want to use networking and how many processes you want to use. You can launch a Domain by simply executing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# syft absolute\n", + "import syft as sy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "node = sy.orchestra.launch(\n", + " name=\"dev-mode-example-domain-1\", port=8020, reset=True, dev_mode=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we don't need a webserver (for development this is true in many cases), we can omit the port and instead use\n", + "\n", + "```python\n", + "node = sy.orchestra.launch(name=\"dev-mode-example-domain-1\", dev_mode=True, reset=True)\n", + "```\n", + "\n", + "One of the benefits of not using a port is that you can use a debugger and set breakpoints within api calls. This makes debugging way faster in many cases." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we are ready to start using the domain. The domain comes with standard login credentials for the admin (just for development)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you are logged in, you are ready to start using the domain, for instance for creating a dataset (this one is empty, just as a example)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", + "client.upload_dataset(dataset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly to stop or terminate your Domain, we can execute the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "node.land()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next Steps\n", + "Congratulations! You have successfully deployed a local in-memory PySyft stack using python. Now, you can explore its capabilities and use cases through our API example notebooks:\n", + "\n", + "πŸ“ [API Example Notebooks](../../api)\n", + "- [00-load-data.ipynb](../../api/0.8/00-load-data.ipynb)\n", + "- [01-submit-code.ipynb](../../api/0.8/01-submit-code.ipynb)\n", + "- [02-review-code-and-approve.ipynb](../../api/0.8/02-review-code-and-approve.ipynb)\n", + "- [03-data-scientist-download-result.ipynb](../../api/0.8/03-data-scientist-download-result.ipynb)\n", + "- [04-jax-example.ipynb](../../api/0.8/04-jax-example.ipynb)\n", + "- [05-custom-policy.ipynb](../../api/0.8/05-custom-policy.ipynb)\n", + "- [06-multiple-code-requests.ipynb](../../api/0.8/06-multiple-code-requests.ipynb)\n", + "- [07-domain-register-control-flow.ipynb](../../api/0.8/07-domain-register-control-flow.ipynb)\n", + "- [08-code-version.ipynb](../../api/0.8/08-code-version.ipynb)\n", + "- [09-blob-storage.ipynb](../../api/0.8/09-blob-storage.ipynb)\n", + "- [10-container-images.ipynb](../../api/0.8/10-container-images.ipynb)\n", + "- [11-container-images-k8s.ipynb](../../api/0.8/11-container-images-k8s.ipynb)\n", + "\n", + "Feel free to explore these notebooks to get started with PySyft and unlock its full potential for privacy-preserving machine learning!" ] } ], "metadata": { + "kernelspec": { + "display_name": "PySyft", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" } }, "nbformat": 4, From 05db9a68b0f8b3e137c59a9c0218a16124bca334 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Sun, 12 May 2024 23:32:31 +0530 Subject: [PATCH 064/114] Add instructions to connect to local k8s cluster --- .../tutorials/deploy/01-deploy-python.ipynb | 15 ++-- .../tutorials/deploy/03-deploy-k8s-k3d.ipynb | 87 ++++++++++++++++++- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/notebooks/tutorials/deploy/01-deploy-python.ipynb b/notebooks/tutorials/deploy/01-deploy-python.ipynb index 76dbc18f7c1..b0de2c53fa3 100644 --- a/notebooks/tutorials/deploy/01-deploy-python.ipynb +++ b/notebooks/tutorials/deploy/01-deploy-python.ipynb @@ -26,7 +26,7 @@ "source": [ "## Prerequisites\n", "Before we begin, ensure you have the following prerequisites installed on your system:\n", - "1. Python 3.10 - 3.12\n", + "1. Python (3.10 - 3.12)\n", "2. pip (or uv)\n", "3. venv (optional, but recommended)" ] @@ -61,7 +61,7 @@ "source": [ "## Working with Python Domain\n", "\n", - "`PySyft` makes it very easy to develop against a domain in a notebook by providing an interface (`sy.orchestra`) that allows you to start a domain with a webserver in a notebook in the background, which is a lightweight version of a Domain that would be used in production. You can specify options such as what kind of database you are using, whether you want to use networking and how many processes you want to use. You can launch a Domain by simply executing:" + "`PySyft` makes it very easy to develop against a domain in a notebook by providing the `sy.orchestra` interface. It allows you to start a domain with a webserver in a notebook in the background, which is a lightweight version of a Domain that would be used in production. You can specify options such as what kind of database you are using, whether you want to use networking and how many processes you want to use. You can launch a Domain by simply executing:" ] }, { @@ -95,14 +95,9 @@ "node = sy.orchestra.launch(name=\"dev-mode-example-domain-1\", dev_mode=True, reset=True)\n", "```\n", "\n", - "One of the benefits of not using a port is that you can use a debugger and set breakpoints within api calls. This makes debugging way faster in many cases." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we are ready to start using the domain. The domain comes with standard login credentials for the admin (just for development)" + "One of the benefits of not using a port is that you can use a debugger and set breakpoints within api calls. This makes debugging way faster in many cases.\n", + "\n", + "Now, we are ready to start using the domain. The domain comes with test login credentials for the admin." ] }, { diff --git a/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb index d1ee9ff67f9..7453f6ee28d 100644 --- a/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb +++ b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb @@ -73,6 +73,77 @@ "```" ] }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "## Working with the local Kubernetes deployment\n", + "\n", + "PySyft makes it very simple to connect to your existing Syft cluster by providing the `sy.orchestra` interface. You can connect to the domain by executing these steps in your jupyter notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# syft absolute\n", + "import syft as sy\n", + "\n", + "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will return a node handle by connecting to `http://localhost:8080` which is the default host and port where your kubernetes cluster will be running. You can connect to a different host and port by setting the environment variables `NODE_URL` and `NODE_PORT`.\n", + "```python\n", + "import os\n", + "\n", + "os.environ[\"NODE_URL\"] = \"\"\n", + "os.environ[\"NODE_PORT\"] = \"\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we are ready to start using the domain. The domain comes with default login credentials for the admin." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you are logged in, you are ready to start using the domain, for instance for creating a dataset (this one is empty, just as a example)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", + "client.upload_dataset(dataset)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -99,8 +170,22 @@ } ], "metadata": { + "kernelspec": { + "display_name": "PySyft", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" } }, "nbformat": 4, From 0812551cde7955b53d2ac8fbff469f2bf838d042 Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Mon, 13 May 2024 15:16:06 +0200 Subject: [PATCH 065/114] update resolve_single tests --- .../syft/src/syft/service/sync/diff_state.py | 9 ++ .../service/sync/sync_resolve_single_test.py | 108 +++++++++++++++++- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/packages/syft/src/syft/service/sync/diff_state.py b/packages/syft/src/syft/service/sync/diff_state.py index d05883db242..dbbb50fedab 100644 --- a/packages/syft/src/syft/service/sync/diff_state.py +++ b/packages/syft/src/syft/service/sync/diff_state.py @@ -1,4 +1,5 @@ # stdlib +from collections.abc import Iterable import html import textwrap from typing import Any @@ -1048,6 +1049,14 @@ def ignored_batches(self) -> list[ObjectDiffBatch]: batch for batch in self.all_batches if batch.decision == SyncDecision.IGNORE ] + @property + def active_batches(self) -> Iterable[ObjectDiffBatch]: + decisions_to_skip = {SyncDecision.IGNORE, SyncDecision.SKIP} + # self.batches might be modified during iteration + for batch in self.batches: + if batch.decision not in decisions_to_skip: + yield batch + @property def ignored_changes(self) -> list[IgnoredBatchView]: result = [] diff --git a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py index bf6cb8aca2d..2f9e8a156e8 100644 --- a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py +++ b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py @@ -5,17 +5,41 @@ import syft import syft as sy from syft.client.domain_client import DomainClient +from syft.client.sync_decision import SyncDecision from syft.client.syncing import compare_clients from syft.client.syncing import resolve_single +from syft.service.code.user_code import UserCode from syft.service.response import SyftSuccess - - -def compare_and_resolve(*, from_client: DomainClient, to_client: DomainClient): +from syft.service.sync.resolve_widget import ResolveWidget + + +def handle_decision(widget: ResolveWidget, decision: SyncDecision): + if decision == SyncDecision.SKIP: + return widget.click_skip() + elif decision == SyncDecision.IGNORE: + return widget.click_ignore() + elif decision in [SyncDecision.LOW, SyncDecision.HIGH]: + return widget.click_sync() + else: + raise ValueError(f"Unknown decision {decision}") + + +def compare_and_resolve( + *, + from_client: DomainClient, + to_client: DomainClient, + decision: SyncDecision = SyncDecision.LOW, + decision_callback: callable = None, +): diff_state_before = compare_clients(from_client, to_client) - for obj_diff_batch in diff_state_before.batches: - widget = resolve_single(obj_diff_batch) + for obj_diff_batch in diff_state_before.active_batches: + widget = resolve_single( + obj_diff_batch=obj_diff_batch, + ) + if decision_callback: + decision = decision_callback(obj_diff_batch) widget.click_share_all_private_data() - res = widget.click_sync() + res = handle_decision(widget, decision) assert isinstance(res, SyftSuccess) from_client.refresh() to_client.refresh() @@ -146,3 +170,75 @@ def compute() -> int: assert len(diff.batches) == 1 assert len(diff.ignored_batches) == 1 assert len(diff.all_batches) == 2 + + +def test_forget_usercode(low_worker, high_worker): + low_client = low_worker.root_client + client_low_ds = low_worker.guest_client + high_client = high_worker.root_client + + @sy.syft_function_single_use() + def compute() -> int: + print("computing...") + return 42 + + _ = client_low_ds.code.request_code_execution(compute) + + diff_before, diff_after = compare_and_resolve( + from_client=low_client, to_client=high_client + ) + + run_and_accept_result(high_client) + + def skip_if_user_code(diff): + if diff.root_type is UserCode: + return SyncDecision.IGNORE + + raise ValueError( + f"Should not reach here after ignoring user code, got {diff.root_type}" + ) + + diff_before, diff_after = compare_and_resolve( + from_client=low_client, + to_client=high_client, + decision_callback=skip_if_user_code, + ) + assert not diff_after.is_same + assert not diff_after.is_same + + +def test_request_code_execution_multiple(low_worker, high_worker): + low_client = low_worker.root_client + client_low_ds = low_worker.guest_client + high_client = high_worker.root_client + + @sy.syft_function_single_use() + def compute() -> int: + return 42 + + @sy.syft_function_single_use() + def compute_twice() -> int: + return 42 * 2 + + @sy.syft_function_single_use() + def compute_thrice() -> int: + return 42 * 3 + + _ = client_low_ds.code.request_code_execution(compute) + _ = client_low_ds.code.request_code_execution(compute_twice) + + diff_before, diff_after = compare_and_resolve( + from_client=low_client, to_client=high_client + ) + + assert not diff_before.is_same + assert diff_after.is_same + + _ = client_low_ds.code.request_code_execution(compute_thrice) + + diff_before, diff_after = compare_and_resolve( + from_client=low_client, to_client=high_client + ) + + assert not diff_before.is_same + assert diff_after.is_same From e1fed8dbf4ae48c7474c5b6080d6bc2bf19e031c Mon Sep 17 00:00:00 2001 From: eelcovdw Date: Mon, 13 May 2024 15:17:00 +0200 Subject: [PATCH 066/114] remove ignore, fix dataset repr + warnings --- .../syft/src/syft/service/dataset/dataset.py | 43 ++++--------------- .../src/syft/service/sync/resolve_widget.py | 34 --------------- 2 files changed, 9 insertions(+), 68 deletions(-) diff --git a/packages/syft/src/syft/service/dataset/dataset.py b/packages/syft/src/syft/service/dataset/dataset.py index b9c8b9426cb..ae685c34b39 100644 --- a/packages/syft/src/syft/service/dataset/dataset.py +++ b/packages/syft/src/syft/service/dataset/dataset.py @@ -2,6 +2,7 @@ from collections.abc import Callable from datetime import datetime from enum import Enum +import textwrap from typing import Any # third party @@ -37,7 +38,6 @@ from ...util.notebook_ui.icons import Icon from ...util.notebook_ui.styles import FONT_CSS from ...util.notebook_ui.styles import ITABLES_CSS -from ...util.util import get_mb_size from ..data_subject.data_subject import DataSubject from ..data_subject.data_subject import DataSubjectCreate from ..data_subject.data_subject_service import DataSubjectService @@ -45,9 +45,6 @@ from ..response import SyftException from ..response import SyftSuccess -DATA_SIZE_WARNING_LIMIT = 512 - - NamePartitionKey = PartitionKey(key="name", type_=str) @@ -329,8 +326,10 @@ class CreateAsset(SyftObject): __repr_attrs__ = ["name"] model_config = ConfigDict(validate_assignment=True) - def __init__(self, description: str | None = "", **data: Any) -> None: - super().__init__(**data, description=MarkdownDescription(text=str(description))) + def __init__(self, description: str | None = None, **data: Any) -> None: + if isinstance(description, str): + description = MarkdownDescription(text=description) + super().__init__(**data, description=description) @model_validator(mode="after") def __mock_is_real_for_empty_mock_must_be_false(self) -> Self: @@ -408,13 +407,6 @@ def check(self) -> SyftSuccess | SyftError: # return SyftError( # message=f"set_obj shape {data_shape} must match set_mock shape {mock_shape}" # ) - total_size_mb = get_mb_size(self.data) + get_mb_size(self.mock) - if total_size_mb > DATA_SIZE_WARNING_LIMIT: - print( - f"**WARNING**: The total size for asset: '{self.name}' exceeds '{DATA_SIZE_WARNING_LIMIT} MB'. " - "This might result in failure to upload dataset. " - "Please contact #support on OpenMined slack for further assistance.", - ) return SyftSuccess(message="Dataset is Valid") @@ -522,32 +514,15 @@ def action_ids(self) -> list[UID]: def assets(self) -> DictTuple[str, Asset]: return DictTuple((asset.name, asset) for asset in self.asset_list) - def _old_repr_markdown_(self) -> str: - _repr_str = f"Syft Dataset: {self.name}\n" - _repr_str += "Assets:\n" - for asset in self.asset_list: - if asset.description is not None: - _repr_str += f"\t{asset.name}: {asset.description.text}\n\n" - else: - _repr_str += f"\t{asset.name}\n\n" - if self.citation: - _repr_str += f"Citation: {self.citation}\n" - if self.url: - _repr_str += f"URL: {self.url}\n" - if self.description: - _repr_str += f"Description: {self.description.text}\n" - return as_markdown_python_code(_repr_str) - def _repr_markdown_(self, wrap_as_python: bool = True, indent: int = 0) -> str: - # return self._old_repr_markdown_() - return self._markdown_() - - def _markdown_(self) -> str: _repr_str = f"Syft Dataset: {self.name}\n\n" _repr_str += "Assets:\n\n" for asset in self.asset_list: if asset.description is not None: - _repr_str += f"\t{asset.name}: {asset.description.text}\n\n" + description_text = textwrap.shorten( + asset.description.text, width=100, placeholder="..." + ) + _repr_str += f"\t{asset.name}: {description_text}\n\n" else: _repr_str += f"\t{asset.name}\n\n" if self.citation: diff --git a/packages/syft/src/syft/service/sync/resolve_widget.py b/packages/syft/src/syft/service/sync/resolve_widget.py index 9aa4c81e19d..dd9dadc505e 100644 --- a/packages/syft/src/syft/service/sync/resolve_widget.py +++ b/packages/syft/src/syft/service/sync/resolve_widget.py @@ -447,40 +447,6 @@ def get_share_private_data_state(self) -> dict[UID, bool]: def get_mockify_state(self) -> dict[UID, bool]: return {uid: widget.mockify for uid, widget in self.id2widget.items()} - def click_ignore(self, *args: list, **kwargs: dict) -> SyftSuccess | SyftError: - # relative - from ...client.syncing import handle_ignore_batch - - if self.is_synced: - return SyftError( - message="The changes in this widget have already been synced." - ) - - res = handle_ignore_batch( - obj_diff_batch=self.obj_diff_batch, - all_batches=self.obj_diff_batch.global_batches, - ) - - self.set_widget_result_state(res) - return res - - def click_unignore(self, *args: list, **kwargs: dict) -> SyftSuccess | SyftError: - # relative - from ...client.syncing import handle_unignore_batch - - if self.is_synced: - return SyftError( - message="The changes in this widget have already been synced." - ) - - res = handle_unignore_batch( - obj_diff_batch=self.obj_diff_batch, - all_batches=self.obj_diff_batch.global_batches, - ) - - self.set_widget_result_state(res) - return res - def click_sync(self, *args: list, **kwargs: dict) -> SyftSuccess | SyftError: # relative from ...client.syncing import handle_sync_batch From 5fc63de309a0056762261f8387dd13a36f9815a3 Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Mon, 13 May 2024 15:17:13 +0200 Subject: [PATCH 067/114] remove skip --- .../syft/tests/syft/service/sync/sync_resolve_single_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py index 2f9e8a156e8..e41d97e5454 100644 --- a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py +++ b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py @@ -14,9 +14,7 @@ def handle_decision(widget: ResolveWidget, decision: SyncDecision): - if decision == SyncDecision.SKIP: - return widget.click_skip() - elif decision == SyncDecision.IGNORE: + if decision == SyncDecision.IGNORE: return widget.click_ignore() elif decision in [SyncDecision.LOW, SyncDecision.HIGH]: return widget.click_sync() From 06b05e09d262b5eec4dab284b57290959f2bc2be Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Mon, 13 May 2024 22:41:30 +0530 Subject: [PATCH 068/114] Add single container deployment tutorial --- .../deploy/02-deploy-container.ipynb | 130 +++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/notebooks/tutorials/deploy/02-deploy-container.ipynb b/notebooks/tutorials/deploy/02-deploy-container.ipynb index 71c158afddd..b45fbeaee20 100644 --- a/notebooks/tutorials/deploy/02-deploy-container.ipynb +++ b/notebooks/tutorials/deploy/02-deploy-container.ipynb @@ -4,7 +4,135 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "TODO" + "# Single container deployment" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "## Introduction\n", + "\n", + "In this deployment, PySyft is encapsulated within a single Docker container, providing better isolation and portability compared to the local Python deployment.\n", + "\n", + "**Recommended For:**\n", + "- Resource-constrained systems with Docker support.\n", + "- Standardizing PySyft deployment across different environments." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "Before we begin, ensure you have [Docker](https://docs.docker.com/install/) installed on your system." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deployment steps\n", + "\n", + "You can execute the below one-liner in your terminal to run the PySyft stack within a single docker container on port `8080`.\n", + "``` bash\n", + "docker run -it -e DEFAULT_ROOT_PASSWORD=secret -e PORT=8080 -p 8080:8080 openmined/grid-enclave:latest\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Working with the single container deployment\n", + "\n", + "PySyft makes it very simple to connect to any existing Syft cluster by providing the `sy.orchestra` interface. You can connect to the domain by executing these steps in your jupyter notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# syft absolute\n", + "import syft as sy\n", + "\n", + "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will return a node handle by connecting to `http://localhost:8080` which is the default host and port where your kubernetes cluster will be running. You can connect to a different host and port by setting the environment variables `NODE_URL` and `NODE_PORT`.\n", + "```python\n", + "import os\n", + "\n", + "os.environ[\"NODE_URL\"] = \"\"\n", + "os.environ[\"NODE_PORT\"] = \"\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we are ready to start using the domain. The domain comes with default login credentials for the admin." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you are logged in, you are ready to start using the domain, for instance for creating a dataset (this one is empty, just as a example)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", + "client.upload_dataset(dataset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next Steps\n", + "Congratulations! You have successfully deployed PySyft on your local Kubernetes cluster. Now, you can explore its capabilities and use cases through our API example notebooks:\n", + "\n", + "πŸ“ [API Example Notebooks](../../api)\n", + "- [00-load-data.ipynb](../../api/0.8/00-load-data.ipynb)\n", + "- [01-submit-code.ipynb](../../api/0.8/01-submit-code.ipynb)\n", + "- [02-review-code-and-approve.ipynb](../../api/0.8/02-review-code-and-approve.ipynb)\n", + "- [03-data-scientist-download-result.ipynb](../../api/0.8/03-data-scientist-download-result.ipynb)\n", + "- [04-jax-example.ipynb](../../api/0.8/04-jax-example.ipynb)\n", + "- [05-custom-policy.ipynb](../../api/0.8/05-custom-policy.ipynb)\n", + "- [06-multiple-code-requests.ipynb](../../api/0.8/06-multiple-code-requests.ipynb)\n", + "- [07-domain-register-control-flow.ipynb](../../api/0.8/07-domain-register-control-flow.ipynb)\n", + "- [08-code-version.ipynb](../../api/0.8/08-code-version.ipynb)\n", + "- [09-blob-storage.ipynb](../../api/0.8/09-blob-storage.ipynb)\n", + "- [10-container-images.ipynb](../../api/0.8/10-container-images.ipynb)\n", + "- [11-container-images-k8s.ipynb](../../api/0.8/11-container-images-k8s.ipynb)\n", + "\n", + "Feel free to explore these notebooks to get started with PySyft and unlock its full potential for privacy-preserving machine learning!" ] } ], From 35a62e9a0aa21be374d45f2e8e409df806a63553 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Mon, 13 May 2024 23:10:22 +0530 Subject: [PATCH 069/114] Fix docker command in single container deployment tutorial --- .../tutorials/deploy/02-deploy-container.ipynb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/notebooks/tutorials/deploy/02-deploy-container.ipynb b/notebooks/tutorials/deploy/02-deploy-container.ipynb index b45fbeaee20..962d5326721 100644 --- a/notebooks/tutorials/deploy/02-deploy-container.ipynb +++ b/notebooks/tutorials/deploy/02-deploy-container.ipynb @@ -38,9 +38,23 @@ "source": [ "## Deployment steps\n", "\n", - "You can execute the below one-liner in your terminal to run the PySyft stack within a single docker container on port `8080`.\n", + "You can execute the below command in your terminal to run the PySyft stack within a single docker container on port `8080`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "``` bash\n", - "docker run -it -e DEFAULT_ROOT_PASSWORD=secret -e PORT=8080 -p 8080:8080 openmined/grid-enclave:latest\n", + "docker run -it \\\n", + " -e NODE_NAME=syft-example-domain-1 \\\n", + " -e NODE_TYPE=domain \\\n", + " -e N_CONSUMERS=1 \\\n", + " -e SINGLE_CONTAINER_MODE=true \\\n", + " -e CREATE_PRODUCER=true \\\n", + " -e INMEMORY_WORKERS=true \\\n", + " -p 8080:80 --add-host=host.docker.internal:host-gateway \\\n", + " --name syft-example-domain-1 openmined/grid-backend:0.8.7-beta.7\n", "```" ] }, From b52a9153b482858f18d60a481a90d78a72e25ce6 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 14 May 2024 14:53:34 +0530 Subject: [PATCH 070/114] disable local node tests temporarily --- .github/workflows/pr-tests-stack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 1dbde3b88f2..f56f4aee758 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -88,7 +88,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.12"] - pytest-modules: ["frontend network local_node container_workload"] + pytest-modules: ["frontend network container_workload"] fail-fast: false runs-on: ${{matrix.os}} From 52ac12ecfb9d48b6df0256976141bae0107e558d Mon Sep 17 00:00:00 2001 From: eelcovdw Date: Tue, 14 May 2024 11:51:11 +0200 Subject: [PATCH 071/114] remove old sync flow, rename resolve_single --- packages/syft/src/syft/client/syncing.py | 310 +------- .../syft/src/syft/service/sync/diff_state.py | 4 +- .../tests/syft/service/sync/sync_flow_test.py | 723 ------------------ .../service/sync/sync_resolve_single_test.py | 12 +- tests/integration/local/twin_api_sync_test.py | 4 +- 5 files changed, 23 insertions(+), 1030 deletions(-) delete mode 100644 packages/syft/tests/syft/service/sync/sync_flow_test.py diff --git a/packages/syft/src/syft/client/syncing.py b/packages/syft/src/syft/client/syncing.py index 45e5c33a837..a48cef05ab3 100644 --- a/packages/syft/src/syft/client/syncing.py +++ b/packages/syft/src/syft/client/syncing.py @@ -1,21 +1,13 @@ # stdlib -from collections.abc import Callable -from time import sleep +import warnings # relative from ..abstract_node import NodeSideType from ..node.credentials import SyftVerifyKey -from ..service.action.action_permissions import ActionObjectPermission -from ..service.action.action_permissions import ActionPermission -from ..service.action.action_permissions import StoragePermission -from ..service.code.user_code import UserCode -from ..service.job.job_stash import Job from ..service.response import SyftError from ..service.response import SyftSuccess from ..service.sync.diff_state import NodeDiff -from ..service.sync.diff_state import ObjectDiff from ..service.sync.diff_state import ObjectDiffBatch -from ..service.sync.diff_state import ResolvedSyncState from ..service.sync.diff_state import SyncInstruction from ..service.sync.resolve_widget import ResolveWidget from ..service.sync.sync_state import SyncState @@ -72,11 +64,20 @@ def get_user_input_for_resolve() -> SyncDecision: print(f"Please choose between {options_str}") -def resolve_single(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget: +def resolve(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget: widget = ResolveWidget(obj_diff_batch) return widget +def resolve_single(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget: + warnings.warn( + "resolve_single has been renamed to resolve", + DeprecationWarning, + stacklevel=1, + ) + return resolve(obj_diff_batch) + + def handle_sync_batch( obj_diff_batch: ObjectDiffBatch, share_private_data: dict[UID, bool], @@ -254,292 +255,3 @@ def get_other_ignore_batches( ignored_ids.update(other_batch_ids) return other_ignore_batches - - -# Old resolve flow -#################################################################################################### - - -def resolve( - state: NodeDiff, - decision: str | None = None, - decision_callback: Callable[[ObjectDiffBatch], SyncDecision] | None = None, - share_private_objects: bool = False, - ask_for_input: bool = True, -) -> tuple[ResolvedSyncState, ResolvedSyncState]: - # TODO: fix this - previously_ignored_batches = state.low_state.ignored_batches - # TODO: only add permissions for objects where we manually give permission - # Maybe default read permission for some objects (high -> low) - resolved_state_low = ResolvedSyncState(node_uid=state.low_node_uid, alias="low") - resolved_state_high = ResolvedSyncState(node_uid=state.high_node_uid, alias="high") - - for batch_diff in state.all_batches: - if batch_diff.is_unchanged: - # Hierarchy has no diffs - continue - - if batch_diff.decision is not None: - # handles ignores - batch_decision = batch_diff.decision - elif decision is not None: - print(batch_diff.__repr__()) - batch_decision = SyncDecision(decision) - elif decision_callback is not None: - batch_decision = decision_callback(batch_diff) - else: - print(batch_diff.__repr__()) - batch_decision = get_user_input_for_resolve() - - batch_diff.decision = batch_decision - - other_batches = [b for b in state.all_batches if b is not batch_diff] - handle_ignore_skip(batch_diff, batch_decision, other_batches) - - if batch_decision not in [SyncDecision.SKIP, SyncDecision.IGNORE]: - sync_instructions = get_sync_instructions_for_batch_items_for_add( - batch_diff, - batch_decision, - share_private_objects=share_private_objects, - ask_for_input=ask_for_input, - ) - else: - sync_instructions = [] - if batch_decision == SyncDecision.IGNORE: - resolved_state_high.add_ignored(batch_diff) - resolved_state_low.add_ignored(batch_diff) - - if ( - batch_diff.root_id in previously_ignored_batches - and batch_diff.decision != SyncDecision.IGNORE - ): - resolved_state_high.add_unignored(batch_diff.root_id) - resolved_state_low.add_unignored(batch_diff.root_id) - - print(f"Decision: Syncing {len(sync_instructions)} objects") - - for sync_instruction in sync_instructions: - resolved_state_low.add_sync_instruction(sync_instruction) - resolved_state_high.add_sync_instruction(sync_instruction) - - print() - print("=" * 100) - print() - - return resolved_state_low, resolved_state_high - - -def handle_ignore_skip( - batch: ObjectDiffBatch, decision: SyncDecision, other_batches: list[ObjectDiffBatch] -) -> None: - # make sure type is SyncDecision at runtime - decision = SyncDecision(decision) - - if decision == SyncDecision.SKIP or decision == SyncDecision.IGNORE: - skipped_or_ignored_ids = { - x.object_id for x in batch.get_dependents(include_roots=False) - } - for other_batch in other_batches: - if other_batch.decision != decision: - # Currently, this is not recursive, in the future it might be - other_batch_ids = { - d.object_id - for d in other_batch.get_dependencies(include_roots=True) - } - if len(other_batch_ids & skipped_or_ignored_ids) != 0: - other_batch.decision = decision - skipped_or_ignored_ids.update(other_batch_ids) - action = "Skipping" if decision == SyncDecision.SKIP else "Ignoring" - print( - f"\n{action} other batch with root {other_batch.root_type.__name__}\n" - ) - - -def get_sync_instructions_for_batch_items_for_add( - batch_diff: ObjectDiffBatch, - decision: SyncDecision, - share_private_objects: bool = False, - ask_for_input: bool = True, -) -> list[SyncInstruction]: - sync_decisions: list[SyncInstruction] = [] - - unpublished_private_high_diffs: list[ObjectDiff] = [] - for diff in batch_diff.get_dependents(include_roots=False): - is_high_private_object = ( - diff.high_obj is not None and diff.high_obj._has_private_sync_attrs() - ) - is_low_published_object = diff.low_node_uid in diff.low_storage_permissions - if is_high_private_object and not is_low_published_object: - unpublished_private_high_diffs.append(diff) - - user_codes_high: list[UserCode] = [ - diff.high_obj - for diff in batch_diff.get_dependencies(include_roots=True) - if isinstance(diff.high_obj, UserCode) - ] - - if len(user_codes_high) == 0: - user_code_high = None - else: - # NOTE we can always assume the first usercode is - # not a nested code, because diffs are sorted in depth-first order - user_code_high = user_codes_high[0] - - if user_code_high is None and len(unpublished_private_high_diffs): - raise ValueError("Found unpublished private objects without user code") - - if share_private_objects: - private_high_diffs_to_share = unpublished_private_high_diffs - elif ask_for_input: - private_high_diffs_to_share = ask_user_input_permission( - user_code_high, unpublished_private_high_diffs - ) - else: - private_high_diffs_to_share = [] - - for diff in batch_diff.get_dependencies(include_roots=False): - is_unpublished_private_diff = diff in unpublished_private_high_diffs - has_share_decision = diff in private_high_diffs_to_share - - if isinstance(diff.high_obj, Job): - if user_code_high is None: - raise ValueError("Job without user code") - # Jobs are always shared - new_permissions_low_side = [ - ActionObjectPermission( - uid=diff.object_id, - permission=ActionPermission.READ, - credentials=user_code_high.user_verify_key, - ) - ] - mockify = False - - elif is_unpublished_private_diff and has_share_decision: - # private + want to share - new_permissions_low_side = [ - ActionObjectPermission( - uid=diff.object_id, - permission=ActionPermission.READ, - credentials=user_code_high.user_verify_key, # type: ignore - ) - ] - mockify = False - - elif is_unpublished_private_diff and not has_share_decision: - # private + do not share - new_permissions_low_side = [] - mockify = True - - else: - # any other object is shared - new_permissions_low_side = [] - mockify = False - - new_storage_permissions_lowside = [] - if not mockify: - new_storage_permissions_lowside = [ - StoragePermission(uid=diff.object_id, node_uid=diff.low_node_uid) - ] - - if ( - diff.status == "NEW" - and diff.high_obj is None - and decision == SyncDecision.LOW - ): - new_storage_permissions_highside = [ - StoragePermission(uid=diff.object_id, node_uid=diff.high_node_uid) - ] - else: - new_storage_permissions_highside = [] - - sync_decisions.append( - SyncInstruction( - diff=diff, - decision=decision, - new_permissions_lowside=new_permissions_low_side, - new_storage_permissions_lowside=new_storage_permissions_lowside, - new_storage_permissions_highside=new_storage_permissions_highside, - mockify=mockify, - ) - ) - - return sync_decisions - - -QUESTION_SHARE_PRIVATE_OBJS = """You currently have the following private objects: - -{objects_str} - -Do you want to share some of these private objects? If so type the first 3 characters of the id e.g. 'abc'. -If you want to share all private objects, type "all". -If you dont want to share any more private objects, type "no". -""" - -CONFIRMATION_SHARE_PRIVATE_OBJ = """Setting permissions for {object_type} #{object_id} to share with {user_verify_key}, -this will become effective when you call client.apply_state()) -""" - - -def ask_user_input_permission( - user_code: UserCode, all_private_high_diffs: list[ObjectDiff] -) -> list[ObjectDiff]: - if len(all_private_high_diffs) == 0: - return [] - - user_verify_key = user_code.user_verify_key - private_high_diffs_to_share = [] - print( - f"""This batch of updates contains new private objects on the high side that you may want \ - to share with user {user_verify_key}.""" - ) - - remaining_private_high_diffs = all_private_high_diffs[:] - while len(remaining_private_high_diffs): - objects_str = "\n".join( - [ - f"{diff.object_type} #{diff.object_id}" - for diff in remaining_private_high_diffs - ] - ) - print(QUESTION_SHARE_PRIVATE_OBJS.format(objects_str=objects_str), flush=True) - - sleep(0.1) - res = input() - if res == "no": - break - - if res == "all": - private_high_diffs_to_share.extend(remaining_private_high_diffs) - remaining_private_high_diffs = [] - elif len(res) >= 3: - matches = [ - diff - for diff in remaining_private_high_diffs - if str(diff.object_id).startswith(res) - ] - if len(matches) == 0: - print("Invalid input") - continue - elif len(matches) == 1: - diff = matches[0] - print() - print("=" * 100) - print() - print( - CONFIRMATION_SHARE_PRIVATE_OBJ.format( - object_type=diff.object_type, - object_id=diff.object_id, - user_verify_key=user_verify_key, - ) - ) - - remaining_private_high_diffs.remove(diff) - private_high_diffs_to_share.append(diff) - - else: - print("Found multiple matches for provided id, exiting") - break - else: - print("invalid input") - - return private_high_diffs_to_share diff --git a/packages/syft/src/syft/service/sync/diff_state.py b/packages/syft/src/syft/service/sync/diff_state.py index dbbb50fedab..014e33f5bc8 100644 --- a/packages/syft/src/syft/service/sync/diff_state.py +++ b/packages/syft/src/syft/service/sync/diff_state.py @@ -807,7 +807,7 @@ def _repr_html_(self) -> str: except Exception as _: return SyftError( message=html.escape( - "Could not render batch, please use resolve_single() instead." + "Could not render batch, please use resolve() instead." ) )._repr_html_() @@ -893,7 +893,7 @@ def __repr__(self) -> Any: except Exception as _: return SyftError( message=html.escape( - "Could not render batch, please use resolve_single() instead." + "Could not render batch, please use resolve() instead." ) )._repr_html_() diff --git a/packages/syft/tests/syft/service/sync/sync_flow_test.py b/packages/syft/tests/syft/service/sync/sync_flow_test.py deleted file mode 100644 index a48cc1a8d5e..00000000000 --- a/packages/syft/tests/syft/service/sync/sync_flow_test.py +++ /dev/null @@ -1,723 +0,0 @@ -# stdlib -import sys - -# third party -import numpy as np -import pytest - -# syft absolute -import syft -import syft as sy -from syft.abstract_node import NodeSideType -from syft.client.domain_client import DomainClient -from syft.client.sync_decision import SyncDecision -from syft.client.syncing import compare_clients -from syft.client.syncing import compare_states -from syft.client.syncing import resolve -from syft.client.syncing import resolve_single -from syft.service.action.action_object import ActionObject -from syft.service.response import SyftError -from syft.service.response import SyftSuccess - - -def compare_and_resolve(*, from_client: DomainClient, to_client: DomainClient): - diff_state_before = compare_clients(from_client, to_client) - for obj_diff_batch in diff_state_before.batches: - widget = resolve_single(obj_diff_batch) - widget.click_share_all_private_data() - res = widget.click_sync() - assert isinstance(res, SyftSuccess) - from_client.refresh() - to_client.refresh() - diff_state_after = compare_clients(from_client, to_client) - return diff_state_before, diff_state_after - - -def run_and_accept_result(client): - job_high = client.code.compute(blocking=True) - client.requests[0].accept_by_depositing_result(job_high) - return job_high - - -@syft.syft_function_single_use() -def compute() -> int: - return 42 - - -def get_ds_client(client: DomainClient) -> DomainClient: - client.register( - name="a", - email="a@a.com", - password="asdf", - password_verify="asdf", - ) - return client.login(email="a@a.com", password="asdf") - - -@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -# @pytest.mark.flaky(reruns=3, reruns_delay=3) -def test_sync_flow(): - # somehow skipif does not work - if sys.platform == "win32": - return - low_worker = sy.Worker( - name="low-test", - local_db=True, - n_consumers=1, - create_producer=True, - node_side_type=NodeSideType.LOW_SIDE, - queue_port=None, - in_memory_workers=True, - ) - high_worker = sy.Worker( - name="high-test", - local_db=True, - n_consumers=1, - create_producer=True, - node_side_type=NodeSideType.HIGH_SIDE, - queue_port=None, - in_memory_workers=True, - ) - - low_client = low_worker.root_client - high_client = high_worker.root_client - - low_client.register( - email="newuser@openmined.org", - name="John Doe", - password="pw", - password_verify="pw", - ) - client_low_ds = low_worker.guest_client - - mock_high = np.array([10, 11, 12, 13, 14]) - private_high = np.array([15, 16, 17, 18, 19]) - - dataset_high = sy.Dataset( - name="my-dataset", - description="abc", - asset_list=[ - sy.Asset( - name="numpy-data", - mock=mock_high, - data=private_high, - shape=private_high.shape, - mock_is_real=True, - ) - ], - ) - - high_client.upload_dataset(dataset_high) - mock_low = np.array([0, 1, 2, 3, 4]) # do_high.mock - - dataset_low = sy.Dataset( - id=dataset_high.id, - name="my-dataset", - description="abc", - asset_list=[ - sy.Asset( - name="numpy-data", - mock=mock_low, - data=ActionObject.empty(data_node_id=high_client.id), - shape=mock_low.shape, - mock_is_real=True, - ) - ], - ) - - res = low_client.upload_dataset(dataset_low) - - data_low = client_low_ds.datasets[0].assets[0] - - @sy.syft_function_single_use(data=data_low) - def compute_mean(data) -> float: - return data.mean() - - res = client_low_ds.code.request_code_execution(compute_mean) - res = client_low_ds.code.request_code_execution(compute_mean) - print(res) - print("LOW CODE:", low_client.code.get_all()) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - print(low_state.objects, high_state.objects) - - diff_state = compare_states(low_state, high_state) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, decision="low", share_private_objects=True - ) - - print(low_items_to_sync, high_items_to_sync) - - low_client.apply_state(low_items_to_sync) - - high_client.apply_state(high_items_to_sync) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - diff_state = compare_states(low_state, high_state) - - high_client._fetch_api(high_client.credentials) - - data_high = high_client.datasets[0].assets[0] - - print(high_client.code.get_all()) - job_high = high_client.code.compute_mean(data=data_high, blocking=False) - print("Waiting for job...") - job_high.wait(timeout=60) - job_high.result.get() - - # syft absolute - from syft.service.request.request import Request - - request: Request = high_client.requests[0] - job_info = job_high.info(public_metadata=True, result=True) - - print(request.syft_client_verify_key, request.syft_node_location) - print(request.code.syft_client_verify_key, request.code.syft_node_location) - request.accept_by_depositing_result(job_info) - - request = high_client.requests[0] - code = request.code - job_high._get_log_objs() - - action_store_high = high_worker.get_service("actionservice").store - blob_store_high = high_worker.get_service("blobstorageservice").stash.partition - assert ( - f"{client_low_ds.verify_key}_READ" - in action_store_high.permissions[job_high.result.id.id] - ) - assert ( - f"{client_low_ds.verify_key}_READ" - in blob_store_high.permissions[job_high.result.syft_blob_storage_entry_id] - ) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - diff_state_2 = compare_states(low_state, high_state) - - low_items_to_sync, high_items_to_sync = resolve( - diff_state_2, decision="high", share_private_objects=True - ) - for diff in diff_state_2.diffs: - print(diff.status, diff.object_type) - low_client.apply_state(low_items_to_sync) - - action_store_low = low_worker.get_service("actionservice").store - blob_store_low = low_worker.get_service("blobstorageservice").stash.partition - assert ( - f"{client_low_ds.verify_key}_READ" - in action_store_low.permissions[job_high.result.id.id] - ) - assert ( - f"{client_low_ds.verify_key}_READ" - in blob_store_low.permissions[job_high.result.syft_blob_storage_entry_id] - ) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - res_low = client_low_ds.code.compute_mean(data=data_low) - print("Res Low", res_low) - - assert res_low.get() == private_high.mean() - - assert ( - res_low.id.id - == job_high.result.id.id - == code.output_history[-1].outputs[0].id.id - ) - assert ( - job_high.result.syft_blob_storage_entry_id == res_low.syft_blob_storage_entry_id - ) - - job_low = client_low_ds.code.compute_mean(data=data_low, blocking=False) - - assert job_low.id == job_high.id - assert job_low.result.id == job_high.result.id - assert ( - job_low.result.syft_blob_storage_entry_id - == job_high.result.syft_blob_storage_entry_id - ) - low_worker.cleanup() - high_worker.cleanup() - - -def test_forget_usercode(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - print("computing...") - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, decision="low", share_private_objects=True - ) - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - high_client.code.get_all() - job_high = high_client.code.compute().get() - # job_info = job_high.info(public_metadata=True, result=True) - - request = high_client.requests[0] - request.accept_by_depositing_result(job_high) - - # job_high._get_log_objs() - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - diff_state_2 = compare_states(low_state, high_state) - - def skip_if_user_code(diff): - if diff.root.object_type == "UserCode": - return SyncDecision.IGNORE - raise Exception(f"Should not reach here, but got {diff.root.object_type}") - - low_items_to_sync, high_items_to_sync = resolve( - diff_state_2, - share_private_objects=True, - decision_callback=skip_if_user_code, - ) - - -@sy.api_endpoint_method() -def mock_function(context) -> str: - return -42 - - -@sy.api_endpoint_method() -def private_function(context) -> str: - return 42 - - -def test_skip_user_code(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - def skip_if_user_code(diff): - if diff.root.object_type == "UserCode": - return SyncDecision.SKIP - raise Exception(f"Should not reach here, but got {diff.root.object_type}") - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - share_private_objects=True, - decision_callback=skip_if_user_code, - ) - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - assert low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - -def test_unignore(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - share_private_objects=True, - decision="ignore", - ) - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - assert low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - diff_state = compare_clients(low_client, high_client) - - for ignored in diff_state.ignored_changes: - deps = ignored.batch.get_dependencies() - if "Request" in [dep.object_type for dep in deps]: - ignored.stage_change() - - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - share_private_objects=True, - decision="low", - ) - - assert not low_items_to_sync.is_empty - assert not high_items_to_sync.is_empty - - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - share_private_objects=True, - decision="low", - ) - - assert diff_state.is_same - - -def test_request_code_execution_multiple(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - @sy.syft_function_single_use() - def compute_twice() -> int: - return 42 * 2 - - @sy.syft_function_single_use() - def compute_thrice() -> int: - return 42 * 3 - - _ = client_low_ds.code.request_code_execution(compute) - _ = client_low_ds.code.request_code_execution(compute_twice) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, decision="low", share_private_objects=True - ) - - assert not diff_state.is_same - assert len(diff_state.diffs) % 2 == 0 - assert not low_items_to_sync.is_empty - assert not high_items_to_sync.is_empty - - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - _ = client_low_ds.code.request_code_execution(compute_thrice) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, decision="low", share_private_objects=True - ) - - assert not diff_state.is_same - assert len(diff_state.diffs) % 3 == 0 - assert not low_items_to_sync.is_empty - assert not high_items_to_sync.is_empty - - -def test_sync_high(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision="high", - ) - - assert not diff_state.is_same - assert not low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - -@pytest.mark.parametrize( - "decision", - ["skip", "ignore"], -) -def test_sync_skip_ignore(low_worker, high_worker, decision): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision=decision, - ) - - assert not diff_state.is_same - assert low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - def should_not_be_called(diff): - # should not be called when decision is ignore before - if decision == "ignore": - raise Exception("Should not reach here") - return SyncDecision.SKIP - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision_callback=should_not_be_called, - ) - - -def test_update_after_ignore(low_worker, high_worker): - low_client = low_worker.root_client - client_low_ds = low_worker.guest_client - high_client = high_worker.root_client - - @sy.syft_function_single_use() - def compute() -> int: - return 42 - - _ = client_low_ds.code.request_code_execution(compute) - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision="ignore", - ) - - assert not diff_state.is_same - assert low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - low_client.apply_state(low_items_to_sync) - high_client.apply_state(high_items_to_sync) - - @sy.syft_function_single_use() - def compute() -> int: - return 43 - - # _ = client_low_ds.code.request_code_execution(compute) - low_client.requests[-1].approve() - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision="low", - ) - - assert not high_items_to_sync.is_empty - - -@pytest.mark.parametrize( - "decision", - ["skip", "ignore", "low", "high"], -) -def test_sync_empty(low_worker, high_worker, decision): - low_client = low_worker.root_client - high_client = high_worker.root_client - - diff_state = compare_clients(low_client, high_client) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, - decision=decision, - ) - - assert diff_state.is_same - assert low_items_to_sync.is_empty - assert high_items_to_sync.is_empty - - -@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -@pytest.mark.flaky(reruns=3, reruns_delay=3) -def test_sync_flow_no_sharing(): - # somehow skipif does not work - if sys.platform == "win32": - return - low_worker = sy.Worker( - name="low-test-2", - local_db=True, - n_consumers=1, - create_producer=True, - node_side_type=NodeSideType.LOW_SIDE, - queue_port=None, - in_memory_workers=True, - ) - high_worker = sy.Worker( - name="high-test-2", - local_db=True, - n_consumers=1, - create_producer=True, - node_side_type=NodeSideType.HIGH_SIDE, - queue_port=None, - in_memory_workers=True, - ) - - low_client = low_worker.root_client - high_client = high_worker.root_client - - low_client.register( - email="newuser@openmined.org", - name="John Doe", - password="pw", - password_verify="pw", - ) - client_low_ds = low_worker.guest_client - - mock_high = np.array([10, 11, 12, 13, 14]) - private_high = np.array([15, 16, 17, 18, 19]) - - dataset_high = sy.Dataset( - name="my-dataset", - description="abc", - asset_list=[ - sy.Asset( - name="numpy-data", - mock=mock_high, - data=private_high, - shape=private_high.shape, - mock_is_real=True, - ) - ], - ) - - high_client.upload_dataset(dataset_high) - mock_low = np.array([0, 1, 2, 3, 4]) # do_high.mock - - dataset_low = sy.Dataset( - id=dataset_high.id, - name="my-dataset", - description="abc", - asset_list=[ - sy.Asset( - name="numpy-data", - mock=mock_low, - data=ActionObject.empty(data_node_id=high_client.id), - shape=mock_low.shape, - mock_is_real=True, - ) - ], - ) - - res = low_client.upload_dataset(dataset_low) - - data_low = client_low_ds.datasets[0].assets[0] - - @sy.syft_function_single_use(data=data_low) - def compute_mean(data) -> float: - return data.mean() - - res = client_low_ds.code.request_code_execution(compute_mean) - print(res) - print("LOW CODE:", low_client.code.get_all()) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - print(low_state.objects, high_state.objects) - - diff_state = compare_states(low_state, high_state) - low_items_to_sync, high_items_to_sync = resolve( - diff_state, decision="low", share_private_objects=True - ) - - print(low_items_to_sync, high_items_to_sync) - - low_client.apply_state(low_items_to_sync) - - high_client.apply_state(high_items_to_sync) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - diff_state = compare_states(low_state, high_state) - - high_client._fetch_api(high_client.credentials) - - data_high = high_client.datasets[0].assets[0] - - print(high_client.code.get_all()) - job_high = high_client.code.compute_mean(data=data_high, blocking=False) - print("Waiting for job...") - job_high.wait(timeout=60) - job_high.result.get() - - # syft absolute - from syft.service.request.request import Request - - request: Request = high_client.requests[0] - job_info = job_high.info(public_metadata=True, result=True) - - print(request.syft_client_verify_key, request.syft_node_location) - print(request.code.syft_client_verify_key, request.code.syft_node_location) - request.accept_by_depositing_result(job_info) - - request = high_client.requests[0] - job_high._get_log_objs() - - action_store_high = high_worker.get_service("actionservice").store - blob_store_high = high_worker.get_service("blobstorageservice").stash.partition - assert ( - f"{client_low_ds.verify_key}_READ" - in action_store_high.permissions[job_high.result.id.id] - ) - assert ( - f"{client_low_ds.verify_key}_READ" - in blob_store_high.permissions[job_high.result.syft_blob_storage_entry_id] - ) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - - diff_state_2 = compare_states(low_state, high_state) - - low_items_to_sync, high_items_to_sync = resolve( - diff_state_2, decision="high", share_private_objects=False, ask_for_input=False - ) - for diff in diff_state_2.diffs: - print(diff.status, diff.object_type) - low_client.apply_state(low_items_to_sync) - - low_state = low_client.get_sync_state() - high_state = high_client.get_sync_state() - res_low = client_low_ds.code.compute_mean(data=data_low) - assert isinstance(res_low, SyftError) - assert ( - res_low.message - == f"Permission: [READ: {job_high.result.id.id} as {client_low_ds.verify_key}] denied" - ) - - job_low = client_low_ds.code.compute_mean(data=data_low, blocking=False) - - assert job_low.id == job_high.id - assert job_low.result.id == job_high.result.id - result = job_low.result.get() - assert isinstance(result, SyftError) - assert ( - result.message - == f"Permission: [READ: {job_high.result.id.id} as {client_low_ds.verify_key}] denied" - ) - - low_worker.cleanup() - high_worker.cleanup() diff --git a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py index 1f666e7dfa0..b3972532521 100644 --- a/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py +++ b/packages/syft/tests/syft/service/sync/sync_resolve_single_test.py @@ -7,15 +7,19 @@ from syft.client.domain_client import DomainClient from syft.client.sync_decision import SyncDecision from syft.client.syncing import compare_clients -from syft.client.syncing import resolve_single +from syft.client.syncing import resolve from syft.service.code.user_code import UserCode +from syft.service.response import SyftError from syft.service.response import SyftSuccess from syft.service.sync.resolve_widget import ResolveWidget -def handle_decision(widget: ResolveWidget, decision: SyncDecision): +def handle_decision( + widget: ResolveWidget, decision: SyncDecision +) -> SyftSuccess | SyftError: if decision == SyncDecision.IGNORE: - return widget.click_ignore() + # ignore not yet implemented on the widget + return widget.obj_diff_batch.ignore() elif decision in [SyncDecision.LOW, SyncDecision.HIGH]: return widget.click_sync() else: @@ -31,7 +35,7 @@ def compare_and_resolve( ): diff_state_before = compare_clients(from_client, to_client) for obj_diff_batch in diff_state_before.active_batches: - widget = resolve_single( + widget = resolve( obj_diff_batch=obj_diff_batch, ) if decision_callback: diff --git a/tests/integration/local/twin_api_sync_test.py b/tests/integration/local/twin_api_sync_test.py index fc2c9f59811..d39066ade9a 100644 --- a/tests/integration/local/twin_api_sync_test.py +++ b/tests/integration/local/twin_api_sync_test.py @@ -10,7 +10,7 @@ import syft as sy from syft.client.domain_client import DomainClient from syft.client.syncing import compare_clients -from syft.client.syncing import resolve_single +from syft.client.syncing import resolve from syft.service.job.job_stash import JobStatus from syft.service.response import SyftError from syft.service.response import SyftSuccess @@ -19,7 +19,7 @@ def compare_and_resolve(*, from_client: DomainClient, to_client: DomainClient): diff_state_before = compare_clients(from_client, to_client) for obj_diff_batch in diff_state_before.batches: - widget = resolve_single(obj_diff_batch) + widget = resolve(obj_diff_batch) widget.click_share_all_private_data() res = widget.click_sync() assert isinstance(res, SyftSuccess) From bff253ed00e676723dfadace24a27cdb058d7d22 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 14 May 2024 16:44:28 +0530 Subject: [PATCH 072/114] fix integration tests --- tests/integration/local/job_test.py | 6 ++---- tox.ini | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index e713da731df..ad835da536b 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -2,7 +2,6 @@ # stdlib from secrets import token_hex -import time # third party import pytest @@ -86,7 +85,7 @@ def job(node): @syft_function() def process_batch(): - # stdlib + import time # noqa while time.sleep(1) is None: ... @@ -95,10 +94,9 @@ def process_batch(): @syft_function_single_use() def process_all(domain): - # stdlib - _ = domain.launch_job(process_batch) _ = domain.launch_job(process_batch) + import time # noqa while time.sleep(1) is None: ... diff --git a/tox.ini b/tox.ini index 48743f1d64a..44c11774739 100644 --- a/tox.ini +++ b/tox.ini @@ -642,6 +642,7 @@ description = Integration Tests for Core Stack using K8s basepython = python3 deps = {[testenv:syft]deps} + {[testenv:hagrid]deps} nbmake changedir = {toxinidir} passenv=HOME, USER, AZURE_BLOB_STORAGE_KEY From f9748cdad4765122d068429fb228ef64dd48513e Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 14 May 2024 16:47:52 +0530 Subject: [PATCH 073/114] Revert "disable local node tests temporarily" This reverts commit b52a9153b482858f18d60a481a90d78a72e25ce6. --- .github/workflows/pr-tests-stack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index f56f4aee758..1dbde3b88f2 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -88,7 +88,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.12"] - pytest-modules: ["frontend network container_workload"] + pytest-modules: ["frontend network local_node container_workload"] fail-fast: false runs-on: ${{matrix.os}} From 6cf33185a41fbd2d77a018c6e59ec5e48261088d Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Tue, 14 May 2024 13:36:55 +0200 Subject: [PATCH 074/114] fix nightlies --- .github/workflows/container-scan.yml | 72 ++++++++++----------- .github/workflows/pr-tests-stack-public.yml | 9 +-- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index f7b5df009ae..303eb11bc40 100644 --- a/.github/workflows/container-scan.yml +++ b/.github/workflows/container-scan.yml @@ -274,30 +274,30 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Snyk CLI to check for security issues - # Snyk can be used to break the build when it detects security issues. - # In this case we want to upload the SAST issues to GitHub Code Scanning - uses: snyk/actions/setup@master - env: - # This is where you will need to introduce the Snyk API token created with your Snyk account - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - - - name: Snyk auth - shell: bash - run: snyk config set api=$SNYK_TOKEN - env: - # This is where you will need to introduce the Snyk API token created with your Snyk account - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - - name: Snyk Container test + uses: snyk/actions/docker@master continue-on-error: true - shell: bash - run: snyk container test mongo:7.0.0 --sarif --sarif-file-output=snyk-code.sarif env: # This is where you will need to introduce the Snyk API token created with your Snyk account SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: mongo:7.0.0 + args: --sarif-file-output=snyk-code.sarif + + # Replace any "undefined" security severity values with 0. The undefined value is used in the case + # of license-related findings, which do not do not indicate a security vulnerability. + # See https://github.com/github/codeql-action/issues/2187 for more context. + - name: Post-process sarif output + run: | + sed -i 's/"security-severity": "undefined"/"security-severity": "0"/g' snyk-code.sarif + + # Replace any "null" security severity values with 0. The undefined value is used in the case + # the NVD CVSS Score is not available. + # See https://github.com/Erikvl87/docker-languagetool/issues/90 and https://github.com/github/codeql-action/issues/2187 for more context. + - name: Post-process sarif output for security severities set to "null" + run: | + sed -i 's/"security-severity": "null"/"security-severity": "0"/g' snyk-code.sarif - # Push the Snyk Code results into GitHub Code Scanning tab - name: Upload result to GitHub Code Scanning uses: github/codeql-action/upload-sarif@v3 with: @@ -352,29 +352,29 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Snyk CLI to check for security issues - # Snyk can be used to break the build when it detects security issues. - # In this case we want to upload the SAST issues to GitHub Code Scanning - uses: snyk/actions/setup@master - env: - # This is where you will need to introduce the Snyk API token created with your Snyk account - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - - - name: Snyk auth - shell: bash - run: snyk config set api=$SNYK_TOKEN - env: - # This is where you will need to introduce the Snyk API token created with your Snyk account - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - - name: Snyk Container test + uses: snyk/actions/docker@master continue-on-error: true - shell: bash - run: snyk container test traefik:v2.11.0 --sarif --sarif-file-output=snyk-code.sarif env: # This is where you will need to introduce the Snyk API token created with your Snyk account SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: traefik:v2.11.0 + args: --sarif-file-output=snyk-code.sarif + + # Replace any "undefined" security severity values with 0. The undefined value is used in the case + # of license-related findings, which do not do not indicate a security vulnerability. + # See https://github.com/github/codeql-action/issues/2187 for more context. + - name: Post-process sarif output + run: | + sed -i 's/"security-severity": "undefined"/"security-severity": "0"/g' snyk-code.sarif + + # Replace any "null" security severity values with 0. The undefined value is used in the case + # the NVD CVSS Score is not available. + # See https://github.com/Erikvl87/docker-languagetool/issues/90 and https://github.com/github/codeql-action/issues/2187 for more context. + - name: Post-process sarif output for security severities set to "null" + run: | + sed -i 's/"security-severity": "null"/"security-severity": "0"/g' snyk-code.sarif # Push the Snyk Code results into GitHub Code Scanning tab - name: Upload result to GitHub Code Scanning diff --git a/.github/workflows/pr-tests-stack-public.yml b/.github/workflows/pr-tests-stack-public.yml index a036d7b5e07..daa36fc1043 100644 --- a/.github/workflows/pr-tests-stack-public.yml +++ b/.github/workflows/pr-tests-stack-public.yml @@ -18,7 +18,8 @@ jobs: strategy: max-parallel: 99 matrix: - os: [ubuntu-latest, macos-latest, windows] + # issues with macos 14 arm https://github.com/crazy-max/ghaction-setup-docker/pull/53 + os: [ubuntu-latest, macos-13, windows] python-version: ["3.12"] pytest-modules: ["frontend network"] fail-fast: false @@ -117,11 +118,11 @@ jobs: chmod +x ~/.docker/cli-plugins/docker-compose - name: Docker on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-latest' - uses: crazy-max/ghaction-setup-docker@v3.1.0 + if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-13' + uses: crazy-max/ghaction-setup-docker@v3.2.0 - name: Docker Compose on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-latest' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-13' shell: bash run: | brew install docker-compose From 27d7e1fe613d5f4dba29384ccfc5d6379543464e Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 14 May 2024 18:37:50 +0530 Subject: [PATCH 075/114] [integration] skip job restart test --- tests/integration/local/job_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index ad835da536b..d44b348ff26 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -16,6 +16,7 @@ from syft.service.response import SyftSuccess +@pytest.mark.skip @pytest.mark.local_node def test_job_restart(job) -> None: job.wait(timeout=2) From 275fcb0df7fee97396a9e9c8fbec0d74a8931d38 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Tue, 14 May 2024 18:48:22 +0530 Subject: [PATCH 076/114] [integration] skip job test --- tests/integration/local/job_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index d44b348ff26..02394b23e36 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -112,6 +112,7 @@ def process_all(domain): job.kill() +@pytest.mark.skip @pytest.mark.local_node def test_job_kill(job) -> None: job.wait(timeout=2) From 70247032c436fe4a40dd8bf3b4dbf5cb3952d9f5 Mon Sep 17 00:00:00 2001 From: Julian Cardonnet Date: Tue, 14 May 2024 11:56:14 -0300 Subject: [PATCH 077/114] [WIP] Add repr for client.settings --- .../src/syft/service/settings/settings.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/syft/src/syft/service/settings/settings.py b/packages/syft/src/syft/service/settings/settings.py index da4eb428d2a..aa4d5d6719c 100644 --- a/packages/syft/src/syft/service/settings/settings.py +++ b/packages/syft/src/syft/service/settings/settings.py @@ -1,5 +1,6 @@ # stdlib from collections.abc import Callable +from typing import Any # relative from ...abstract_node import NodeSideType @@ -16,6 +17,8 @@ from ...types.transforms import drop from ...types.transforms import make_set_default from ...types.uid import UID +from ...util import options +from ...util.colors import SURFACE @serializable() @@ -74,6 +77,23 @@ class NodeSettings(SyftObject): association_request_auto_approval: bool default_worker_pool: str = DEFAULT_WORKER_POOL_NAME + def _repr_html_(self) -> Any: + return f""" + +

+

Settings

+

Id: {self.id}

+

Name: {self.name}

+

Organization: {self.organization}

+

Deployed on: {self.deployed_on}

+

Signup enabled: {self.signup_enabled}

+

Admin email: {self.admin_email}

+
+ + """ + @serializable() class NodeSettingsV2(SyftObject): From 9032c4c423aeb5fb1e0b522e43edd7fb5bd9bc06 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 14 May 2024 20:35:22 +0530 Subject: [PATCH 078/114] fixes --- packages/grid/backend/grid/start.sh | 2 -- packages/grid/devspace.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/grid/backend/grid/start.sh b/packages/grid/backend/grid/start.sh index 9823620fe6a..297f242ff78 100755 --- a/packages/grid/backend/grid/start.sh +++ b/packages/grid/backend/grid/start.sh @@ -25,8 +25,6 @@ then DEBUG_CMD="python -m debugpy --listen 0.0.0.0:5678 -m" fi -source $APPDIR/.venv/bin/activate - export CREDENTIALS_PATH=${CREDENTIALS_PATH:-$HOME/data/creds/credentials.json} export NODE_PRIVATE_KEY=$(python $APPDIR/grid/bootstrap.py --private_key) export NODE_UID=$(python $APPDIR/grid/bootstrap.py --uid) diff --git a/packages/grid/devspace.yaml b/packages/grid/devspace.yaml index c047d19d99d..60ca14778c3 100644 --- a/packages/grid/devspace.yaml +++ b/packages/grid/devspace.yaml @@ -36,7 +36,7 @@ images: backend: image: "${CONTAINER_REGISTRY}/${DOCKER_IMAGE_BACKEND}" buildKit: - args: ["--target", "backend", "--platform", "linux/${PLATFORM}"] + args: ["--platform", "linux/${PLATFORM}"] dockerfile: ./backend/backend.dockerfile target: "backend" context: ../ From ce246215a336bccfd2b40853dcc14a2e80679f03 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Tue, 14 May 2024 20:42:23 +0530 Subject: [PATCH 079/114] drop telemetry --- packages/grid/backend/backend.dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index c751c04b4f0..f5c68b3fcb3 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -34,7 +34,7 @@ COPY syft/src/syft/VERSION ./syft/src/syft/ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ # remove torch because we already have the cpu version pre-installed sed --in-place /torch==/d ./syft/setup.cfg && \ - uv pip install -e ./syft[data_science,telemetry] && \ + uv pip install -e ./syft[data_science] && \ uv pip freeze | grep ansible | xargs uv pip uninstall # ==================== [Final] Setup Syft Server ==================== # From f9044ef33b2745660afa984810bc3c5d83983e88 Mon Sep 17 00:00:00 2001 From: Julian Cardonnet Date: Tue, 14 May 2024 16:38:22 -0300 Subject: [PATCH 080/114] Allow getting settings view without calling .get() --- packages/syft/src/syft/client/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index d60c6460b4f..a92957cd959 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -637,6 +637,9 @@ def __getitem__(self, key: str | int) -> Any: raise NotImplementedError def _repr_html_(self) -> Any: + if self.path == "settings": + return self.get()._repr_html_() + if not hasattr(self, "get_all"): return NotImplementedError results = self.get_all() From d3b677482e59059ad59dc56dbefa39fb41e6b7bc Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 10:00:27 +0530 Subject: [PATCH 081/114] Revert "[integration] skip job test" This reverts commit 275fcb0df7fee97396a9e9c8fbec0d74a8931d38. --- tests/integration/local/job_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index 02394b23e36..d44b348ff26 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -112,7 +112,6 @@ def process_all(domain): job.kill() -@pytest.mark.skip @pytest.mark.local_node def test_job_kill(job) -> None: job.wait(timeout=2) From 5f930a0fab81193f03b701377a9a8da25df4c941 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 10:00:37 +0530 Subject: [PATCH 082/114] Revert "[integration] skip job restart test" This reverts commit 27d7e1fe613d5f4dba29384ccfc5d6379543464e. --- tests/integration/local/job_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index d44b348ff26..ad835da536b 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -16,7 +16,6 @@ from syft.service.response import SyftSuccess -@pytest.mark.skip @pytest.mark.local_node def test_job_restart(job) -> None: job.wait(timeout=2) From 171fff80e1e489630469d5fd43a2c9e6b60968bf Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 10:55:26 +0530 Subject: [PATCH 083/114] ignore gateway local test.py --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 44c11774739..a8366180517 100644 --- a/tox.ini +++ b/tox.ini @@ -724,7 +724,7 @@ commands = PYTEST_MODULES=($PYTEST_MODULES); \ for i in "${PYTEST_MODULES[@]}"; do \ echo "Starting test for $i"; date; \ - pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no --ignore=tests/integration/local/gateway_local_test.py; \ return=$?; \ echo "Finished $i"; \ date; \ From cc4713fc98c22eba2b696e11b2c9906e17c46454 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 10:56:38 +0530 Subject: [PATCH 084/114] Revert "ignore gateway local test.py" This reverts commit 171fff80e1e489630469d5fd43a2c9e6b60968bf. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a8366180517..44c11774739 100644 --- a/tox.ini +++ b/tox.ini @@ -724,7 +724,7 @@ commands = PYTEST_MODULES=($PYTEST_MODULES); \ for i in "${PYTEST_MODULES[@]}"; do \ echo "Starting test for $i"; date; \ - pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no --ignore=tests/integration/local/gateway_local_test.py; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ return=$?; \ echo "Finished $i"; \ date; \ From d39b3e51c288fce1495183dd153e5a4f15629a60 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 11:05:18 +0530 Subject: [PATCH 085/114] ignore gateway local test.py --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 44c11774739..1c16d2d7fbd 100644 --- a/tox.ini +++ b/tox.ini @@ -664,6 +664,7 @@ setenv = GATEWAY_CLUSTER_NAME = {env:GATEWAY_CLUSTER_NAME:test-gateway-1} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} SYFT_BASE_IMAGE_REGISTRY = {env:SYFT_BASE_IMAGE_REGISTRY:k3d-registry.localhost:5800} + PYTEST_FLAGS = {env:PYTEST_FLAGS:--ignore=tests/integration/local/gateway_local_test.py} commands = bash -c "echo Running with GITHUB_CI=$GITHUB_CI; date" python -c 'import syft as sy; sy.stage_protocol_changes()' @@ -724,7 +725,7 @@ commands = PYTEST_MODULES=($PYTEST_MODULES); \ for i in "${PYTEST_MODULES[@]}"; do \ echo "Starting test for $i"; date; \ - pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no $PYTEST_FLAGS; \ return=$?; \ echo "Finished $i"; \ date; \ From 65029ea07821f835adb9c99b1b666a74646899fa Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 11:20:28 +0530 Subject: [PATCH 086/114] add new workflow syft.test.integration to test local tests --- .github/workflows/pr-tests-stack.yml | 65 ++++++++++++++++++++++++++++ tox.ini | 30 +++++++++++++ 2 files changed, 95 insertions(+) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 1dbde3b88f2..961b86ae886 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -82,6 +82,71 @@ jobs: run: | tox -e backend.test.basecpu + pr-tests-syft-integration: + strategy: + max-parallel: 99 + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + pytest-modules: ["local_node"] + fail-fast: false + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v4 + + - name: Check for file changes + uses: dorny/paths-filter@v3 + id: changes + with: + base: ${{ github.ref }} + token: ${{ github.token }} + filters: .github/file-filters.yml + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + if: steps.changes.outputs.stack == 'true' + with: + python-version: ${{ matrix.python-version }} + + - name: Upgrade pip + if: steps.changes.outputs.stack == 'true' + run: | + pip install --upgrade pip uv==0.1.35 + uv --version + + - name: Get pip cache dir + if: steps.changes.outputs.stack == 'true' + id: pip-cache + shell: bash + run: | + echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT + + - name: pip cache + uses: actions/cache@v4 + if: steps.changes.outputs.stack == 'true' + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-uv-py${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-uv-py${{ matrix.python-version }} + + - name: Install tox + if: steps.changes.outputs.stack == 'true' + run: | + pip install --upgrade tox tox-uv==1.5.1 + + - name: Run Syft Integration Tests + if: steps.changes.outputs.stack == 'true' + timeout-minutes: 60 + env: + PYTEST_MODULES: "${{ matrix.pytest-modules }}" + GITHUB_CI: true + shell: bash + run: | + tox -e syft.test.integration + pr-tests-integration-k8s: strategy: max-parallel: 99 diff --git a/tox.ini b/tox.ini index 1c16d2d7fbd..c2dd7515099 100644 --- a/tox.ini +++ b/tox.ini @@ -636,6 +636,36 @@ commands = python_version = 3.12 disable_error_code = attr-defined, valid-type, no-untyped-call, arg-type +[testenv:syft.test.integration] +description = Integration Tests for Syft Stack +basepython = python3 +deps = + {[testenv:syft]deps} + {[testenv:hagrid]deps} +changedir = {toxinidir} +passenv=HOME, USER +allowlist_externals = + bash +setenv = + PYTEST_MODULES = {env:PYTEST_MODULES:local_node} + ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} + PYTEST_FLAGS = {env:PYTEST_FLAGS:--ignore=tests/integration/local/gateway_local_test.py} +commands = + python -c 'import syft as sy; sy.stage_protocol_changes()' + + # Run Integration Tests + bash -c '\ + PYTEST_MODULES=($PYTEST_MODULES); \ + for i in "${PYTEST_MODULES[@]}"; do \ + echo "Starting test for $i"; date; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no $PYTEST_FLAGS; \ + return=$?; \ + echo "Finished $i"; \ + date; \ + if [[ $return -ne 0 ]]; then \ + exit $return; \ + fi; \ + done' [testenv:stack.test.integration.k8s] description = Integration Tests for Core Stack using K8s From 3466e4d540664b613830c6277491abfc030aaf43 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 11:21:35 +0530 Subject: [PATCH 087/114] remove local node tests from k8s --- .github/workflows/pr-tests-stack.yml | 2 +- tox.ini | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-tests-stack.yml b/.github/workflows/pr-tests-stack.yml index 961b86ae886..34620e3fa80 100644 --- a/.github/workflows/pr-tests-stack.yml +++ b/.github/workflows/pr-tests-stack.yml @@ -153,7 +153,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.12"] - pytest-modules: ["frontend network local_node container_workload"] + pytest-modules: ["frontend network container_workload"] fail-fast: false runs-on: ${{matrix.os}} diff --git a/tox.ini b/tox.ini index c2dd7515099..892a656e851 100644 --- a/tox.ini +++ b/tox.ini @@ -689,12 +689,11 @@ allowlist_externals = setenv = NODE_PORT = {env:NODE_PORT:9082} GITHUB_CI = {env:GITHUB_CI:false} - PYTEST_MODULES = {env:PYTEST_MODULES:frontend network local_node container_workload} + PYTEST_MODULES = {env:PYTEST_MODULES:frontend network container_workload} DOMAIN_CLUSTER_NAME = {env:DOMAIN_CLUSTER_NAME:test-domain-1} GATEWAY_CLUSTER_NAME = {env:GATEWAY_CLUSTER_NAME:test-gateway-1} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} SYFT_BASE_IMAGE_REGISTRY = {env:SYFT_BASE_IMAGE_REGISTRY:k3d-registry.localhost:5800} - PYTEST_FLAGS = {env:PYTEST_FLAGS:--ignore=tests/integration/local/gateway_local_test.py} commands = bash -c "echo Running with GITHUB_CI=$GITHUB_CI; date" python -c 'import syft as sy; sy.stage_protocol_changes()' @@ -755,7 +754,7 @@ commands = PYTEST_MODULES=($PYTEST_MODULES); \ for i in "${PYTEST_MODULES[@]}"; do \ echo "Starting test for $i"; date; \ - pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no $PYTEST_FLAGS; \ + pytest tests/integration -m $i -vvvv -p no:randomly -p no:benchmark -o log_cli=True --capture=no; \ return=$?; \ echo "Finished $i"; \ date; \ From af97f193446671c4e4596919cefb3e669a7e9840 Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Wed, 15 May 2024 07:58:15 +0200 Subject: [PATCH 088/114] rename windows runner --- .github/workflows/pr-tests-stack-public.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-tests-stack-public.yml b/.github/workflows/pr-tests-stack-public.yml index daa36fc1043..cf8fbedabd0 100644 --- a/.github/workflows/pr-tests-stack-public.yml +++ b/.github/workflows/pr-tests-stack-public.yml @@ -19,7 +19,7 @@ jobs: max-parallel: 99 matrix: # issues with macos 14 arm https://github.com/crazy-max/ghaction-setup-docker/pull/53 - os: [ubuntu-latest, macos-13, windows] + os: [ubuntu-latest, macos-13, windows-latest] python-version: ["3.12"] pytest-modules: ["frontend network"] fail-fast: false @@ -28,7 +28,7 @@ jobs: steps: - name: "clean .git/config" - if: matrix.os == 'windows' + if: matrix.os == 'windows-latest' continue-on-error: true shell: bash run: | @@ -79,31 +79,31 @@ jobs: pip install --upgrade tox tox-uv==1.5.1 - name: Show choco installed packages - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' uses: crazy-max/ghaction-chocolatey@v3 with: args: list --localonly - name: Install git - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' uses: crazy-max/ghaction-chocolatey@v3 with: args: install git.install --params "/GitAndUnixToolsOnPath /WindowsTerminal /NoAutoCrlf" -y - name: Install cmake - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' uses: crazy-max/ghaction-chocolatey@v3 with: args: install cmake.portable --installargs 'ADD_CMAKE_TO_PATH=System' -y - name: Check cmake version - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' run: | cmake --version shell: cmd - name: Install visualcpp-build-tools - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows' + if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' uses: crazy-max/ghaction-chocolatey@v3 with: args: install visualstudio2019-workload-vctools -y @@ -163,7 +163,7 @@ jobs: continue-on-error: true - name: Reboot node - if: matrix.os == 'windows' && failure() + if: matrix.os == 'windows-latest' && failure() run: | shutdown /r /t 1 From 042ce6e55ebd8bbf1509c6c00aa8dce8479290a4 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 11:29:03 +0530 Subject: [PATCH 089/114] revert job_test.py changes --- tests/integration/local/job_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/integration/local/job_test.py b/tests/integration/local/job_test.py index ad835da536b..e713da731df 100644 --- a/tests/integration/local/job_test.py +++ b/tests/integration/local/job_test.py @@ -2,6 +2,7 @@ # stdlib from secrets import token_hex +import time # third party import pytest @@ -85,7 +86,7 @@ def job(node): @syft_function() def process_batch(): - import time # noqa + # stdlib while time.sleep(1) is None: ... @@ -94,9 +95,10 @@ def process_batch(): @syft_function_single_use() def process_all(domain): + # stdlib + _ = domain.launch_job(process_batch) _ = domain.launch_job(process_batch) - import time # noqa while time.sleep(1) is None: ... From 4d52fd2f15770ed3857de2be8fe8848c8b01c7ac Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Wed, 15 May 2024 11:30:31 +0530 Subject: [PATCH 090/114] skip job tests --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 892a656e851..375ea2a39b8 100644 --- a/tox.ini +++ b/tox.ini @@ -649,7 +649,7 @@ allowlist_externals = setenv = PYTEST_MODULES = {env:PYTEST_MODULES:local_node} ASSOCIATION_REQUEST_AUTO_APPROVAL = {env:ASSOCIATION_REQUEST_AUTO_APPROVAL:true} - PYTEST_FLAGS = {env:PYTEST_FLAGS:--ignore=tests/integration/local/gateway_local_test.py} + PYTEST_FLAGS = {env:PYTEST_FLAGS:--ignore=tests/integration/local/gateway_local_test.py --ignore=tests/integration/local/job_test.py} commands = python -c 'import syft as sy; sy.stage_protocol_changes()' From 7b236dba4fcd2474dc92a863df9095ec3b57a0de Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Wed, 15 May 2024 08:00:52 +0200 Subject: [PATCH 091/114] rename windows runner image --- .github/workflows/pr-tests-syft.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-tests-syft.yml b/.github/workflows/pr-tests-syft.yml index cc8fcb00ecb..1083357f141 100644 --- a/.github/workflows/pr-tests-syft.yml +++ b/.github/workflows/pr-tests-syft.yml @@ -39,7 +39,7 @@ jobs: # run: | # sudo chown -R $USER:$USER $HOME - name: "clean .git/config" - if: matrix.os == 'windows' + if: matrix.os == 'windows-latest' continue-on-error: true shell: bash run: | @@ -134,7 +134,7 @@ jobs: # run: | # sudo chown -R $USER:$USER $HOME - name: "clean .git/config" - if: matrix.os == 'windows' + if: matrix.os == 'windows-latest' continue-on-error: true shell: bash run: | From ef217b9598f05b2b8909d727316e8213097d6320 Mon Sep 17 00:00:00 2001 From: alfred-openmined-bot <145415986+alfred-openmined-bot@users.noreply.github.com> Date: Wed, 15 May 2024 06:33:53 +0000 Subject: [PATCH 092/114] bump protocol and remove notebooks --- notebooks/Experimental/Test.ipynb | 3600 ----------------------------- 1 file changed, 3600 deletions(-) delete mode 100644 notebooks/Experimental/Test.ipynb diff --git a/notebooks/Experimental/Test.ipynb b/notebooks/Experimental/Test.ipynb deleted file mode 100644 index c766818d73f..00000000000 --- a/notebooks/Experimental/Test.ipynb +++ /dev/null @@ -1,3600 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "adc7a8fc-fad9-4703-b918-e0145fb324cb", - "metadata": {}, - "outputs": [], - "source": [ - "# stdlib\n", - "import os\n", - "\n", - "# third party\n", - "import requests\n", - "\n", - "# syft absolute\n", - "import syft as sy\n", - "from syft.client.domain_client import DomainClient\n", - "from syft.custom_worker.config import DockerWorkerConfig\n", - "from syft.service.request.request import Request\n", - "from syft.service.response import SyftSuccess\n", - "from syft.service.worker.worker_image import SyftWorkerImage\n", - "from syft.service.worker.worker_pool import SyftWorker\n", - "from syft.service.worker.worker_pool import WorkerPool" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "46da9304-1f02-453d-9caf-c5ab00f0d469", - "metadata": {}, - "outputs": [], - "source": [ - "registry = \"k3d-registry.localhost:5800\"\n", - "repo = \"openmined/grid-backend\"\n", - "\n", - "if \"k3d\" in registry:\n", - " res = requests.get(url=f\"http://{registry}/v2/{repo}/tags/list\")\n", - " tag = res.json()[\"tags\"][0]\n", - "else:\n", - " tag = sy.__version__\n", - "\n", - "external_registry = os.getenv(\"EXTERNAL_REGISTRY\", registry)\n", - "external_registry_username = os.getenv(\"EXTERNAL_REGISTRY_USERNAME\", None)\n", - "external_registry_password = os.getenv(\"EXTERNAL_REGISTRY_PASSWORD\", None)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b9714331-a33b-4008-8795-8cfd98dfdc92", - "metadata": {}, - "outputs": [], - "source": [ - "def test():\n", - " domain_client: DomainClient = sy.login(\n", - " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", - " )\n", - " image_registry_list = domain_client.api.services.image_registry.get_all()\n", - " if len(image_registry_list) > 1:\n", - " raise Exception(\"Only one registry should be present for testing\")\n", - "\n", - " elif len(image_registry_list) == 1:\n", - " assert (\n", - " image_registry_list[0].url == external_registry\n", - " ), \"External registry different from the one set in the environment variable\"\n", - " return image_registry_list[0].id\n", - " else:\n", - " registry_add_result = domain_client.api.services.image_registry.add(\n", - " external_registry\n", - " )\n", - "\n", - " assert isinstance(registry_add_result, sy.SyftSuccess), str(registry_add_result)\n", - "\n", - " image_registry_list = domain_client.api.services.image_registry.get_all()\n", - " return image_registry_list[0].id" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7d5e989c-80e2-45a1-9c6b-ef4d3e1eafe4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "domain_client: DomainClient = sy.login(\n", - " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "6c2cb495-d6bb-4956-a1d0-54daf2a59282", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "

SyftImageRegistry List

\n", - "
\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - "\n", - "

0

\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n" - ], - "text/plain": [ - "[SyftImageRegistry(url=k3d-registry.localhost:5800)]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "domain_client.api.services.image_registry.get_all()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "9acd6fbe-b94e-4fcf-b8ab-2d4fbbedc1e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "test() == test()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "4c4620ba-e890-4fd6-835b-6b90a94fd01c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "domain_client = sy.login(port=9082, email=\"info@openmined.org\", password=\"changethis\")\n", - "\n", - "# Submit Docker Worker Config\n", - "docker_config_rl = f\"\"\"\n", - " FROM {registry}/{repo}:{tag}\n", - " RUN pip install recordlinkage\n", - "\"\"\"\n", - "docker_config = DockerWorkerConfig(dockerfile=docker_config_rl)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a966b192-35d8-450b-98ed-8d65cb651924", - "metadata": {}, - "outputs": [], - "source": [ - "# Submit Worker Image\n", - "submit_result = domain_client.api.services.worker_image.submit_dockerfile(\n", - " docker_config=docker_config\n", - ")\n", - "assert isinstance(submit_result, SyftSuccess)\n", - "assert len(domain_client.images.get_all()) == 2" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "286a2155-329d-49d8-9f65-72a701194ddd", - "metadata": {}, - "outputs": [], - "source": [ - "# Validate if we can get the worker image object from its config\n", - "workerimage = domain_client.api.services.worker_image.get_by_config(docker_config)\n", - "assert not isinstance(workerimage, sy.SyftError)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "c10f9759-0a57-4b58-8877-b5a16db50959", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Build docker image\n", - "docker_tag = \"openmined/custom-worker-rl:latest\"\n", - "docker_build_result = domain_client.api.services.worker_image.build(\n", - " image_uid=workerimage.id,\n", - " tag=docker_tag,\n", - " registry_uid=test(),\n", - ")\n", - "assert isinstance(docker_build_result, SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "b6134199-4471-4cca-96f0-2b82790b1e6c", - "metadata": {}, - "outputs": [], - "source": [ - "# Refresh the worker image object\n", - "workerimage = domain_client.images.get_by_uid(workerimage.id)\n", - "assert not isinstance(workerimage, sy.SyftSuccess)\n", - "\n", - "assert workerimage.is_built\n", - "assert workerimage.image_identifier is not None\n", - "assert workerimage.image_identifier.repo_with_tag == docker_tag\n", - "assert workerimage.image_hash is not None" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "2ed3b344-4530-45ea-ba32-5c695449df85", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "domain_client: DomainClient = sy.login(\n", - " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", - ")\n", - "assert len(domain_client.worker_pools.get_all()) == 1" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "9400b08e-295a-47c5-a63c-a0ac0f2249a8", - "metadata": {}, - "outputs": [], - "source": [ - "# Submit Docker Worker Config\n", - "docker_config_opendp = f\"\"\"\n", - " FROM {registry}/{repo}:{tag}\n", - " RUN pip install opendp\n", - "\"\"\"\n", - "docker_config = DockerWorkerConfig(dockerfile=docker_config_opendp)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "0366da62-29d4-4018-9849-c327f6d27fb5", - "metadata": {}, - "outputs": [], - "source": [ - "# Submit Worker Image\n", - "submit_result = domain_client.api.services.worker_image.submit_dockerfile(\n", - " docker_config=docker_config\n", - ")\n", - "assert isinstance(submit_result, SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "8a1abb0b-65fd-4af7-85eb-ae505e34b572", - "metadata": {}, - "outputs": [], - "source": [ - "worker_image = domain_client.api.services.worker_image.get_by_config(docker_config)\n", - "assert not isinstance(worker_image, sy.SyftError)\n", - "assert worker_image is not None\n", - "assert not worker_image.is_built" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "07d107b2-fc54-4d93-b226-70e8349b7263", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Build docker image\n", - "docker_tag = \"openmined/custom-worker-opendp:latest\"\n", - "docker_build_result = domain_client.api.services.worker_image.build(\n", - " image_uid=worker_image.id, tag=docker_tag, registry_uid=test()\n", - ")\n", - "assert isinstance(docker_build_result, SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "3bedaed8-8a6e-4e16-9e99-20a8368ac29b", - "metadata": {}, - "outputs": [], - "source": [ - "push_result = None\n", - "push_result = domain_client.api.services.worker_image.push(\n", - " worker_image.id,\n", - " username=external_registry_username,\n", - " password=external_registry_password,\n", - ")\n", - "assert isinstance(push_result, sy.SyftSuccess), str(push_result)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "4bb00397-2540-4fa1-866d-f7f177a79bcc", - "metadata": {}, - "outputs": [], - "source": [ - "# Launch a worker pool\n", - "worker_pool_name = \"custom-worker-pool-ver-1\"\n", - "worker_pool_res = domain_client.api.services.worker_pool.launch(\n", - " name=worker_pool_name,\n", - " image_uid=worker_image.id,\n", - " num_workers=3,\n", - ")\n", - "assert len(worker_pool_res) == 3\n", - "\n", - "assert all(worker.error is None for worker in worker_pool_res)\n", - "assert len(domain_client.worker_pools.get_all()) == 2" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "549f8009-d6b4-41e4-87ae-b20709c38459", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "

ContainerSpawnStatus List

\n", - "
\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - "\n", - "

0

\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n" - ], - "text/plain": [ - "[ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-0', worker=syft.service.worker.worker_pool.SyftWorker, error=None),\n", - " ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-1', worker=syft.service.worker.worker_pool.SyftWorker, error=None),\n", - " ContainerSpawnStatus(worker_name='custom-worker-pool-ver-1-2', worker=syft.service.worker.worker_pool.SyftWorker, error=None)]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "worker_pool_res" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "1bd2608c-cb1d-403c-886b-041d530295e2", - "metadata": {}, - "outputs": [], - "source": [ - "worker_pool = domain_client.worker_pools[worker_pool_name]\n", - "assert len(worker_pool.worker_list) == 3\n", - "\n", - "workers = worker_pool.workers\n", - "assert len(workers) == 3\n", - "\n", - "for worker in workers:\n", - " assert worker.worker_pool_name == worker_pool_name\n", - " assert worker.image.id == worker_image.id\n", - "\n", - "assert len(worker_pool.healthy_workers) == 3\n", - "\n", - "# Grab the first worker\n", - "first_worker = workers[0]\n", - "\n", - "# Check worker Logs\n", - "logs = domain_client.api.services.worker.logs(uid=first_worker.id)\n", - "assert not isinstance(logs, sy.SyftError)\n", - "\n", - "# Check for worker status\n", - "status_res = domain_client.api.services.worker.status(uid=first_worker.id)\n", - "assert not isinstance(status_res, sy.SyftError)\n", - "assert isinstance(status_res, tuple)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "127cb09a-1dee-4103-aa9d-1e170126644d", - "metadata": {}, - "outputs": [], - "source": [ - "# Delete the pool's workers\n", - "for worker in worker_pool.workers:\n", - " res = domain_client.api.services.worker.delete(uid=worker.id, force=True)\n", - " assert isinstance(res, sy.SyftSuccess)\n", - "\n", - "# TODO: delete the launched pool" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "7d06b8e8-cdea-47e7-a5a2-3113fd814f4c", - "metadata": {}, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[32], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Clean the build images\u001b[39;00m\n\u001b[1;32m 2\u001b[0m delete_result \u001b[38;5;241m=\u001b[39m domain_client\u001b[38;5;241m.\u001b[39mapi\u001b[38;5;241m.\u001b[39mservices\u001b[38;5;241m.\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mremove(uid\u001b[38;5;241m=\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mid)\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(delete_result, sy\u001b[38;5;241m.\u001b[39mSyftSuccess)\n", - "\u001b[0;31mAssertionError\u001b[0m: " - ] - } - ], - "source": [ - "# Clean the build images\n", - "delete_result = domain_client.api.services.worker_image.remove(uid=worker_image.id)\n", - "assert isinstance(delete_result, sy.SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "520e8f10-9447-4ff6-8fe4-57aa2281ad49", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - } - ], - "source": [ - "domain_client: DomainClient = sy.login(\n", - " port=9082, email=\"info@openmined.org\", password=\"changethis\"\n", - ")\n", - "\n", - "ds_username = \"sheldon\"\n", - "ds_email = ds_username + \"@example.com\"\n", - "res = domain_client.register(\n", - " name=ds_username,\n", - " email=ds_email,\n", - " password=\"secret_pw\",\n", - " password_verify=\"secret_pw\",\n", - ")\n", - "# assert isinstance(res, SyftSuccess)\n", - "ds_client = sy.login(email=ds_email, password=\"secret_pw\", port=9082)" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "758972d7-31d6-48b8-85ee-fe3f4cbb0d41", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logged into as \n" - ] - }, - { - "data": { - "text/html": [ - "
SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`.

" - ], - "text/plain": [ - "SyftWarning: You are using a default password. Please change the password using `[your_client].me.set_password([new_password])`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# the DS makes a request to create an image and a pool based on the image\n", - "docker_config_np = f\"\"\"\n", - " FROM {registry}/{repo}:{tag}\n", - " RUN pip install numpy\n", - "\"\"\"\n", - "docker_config = DockerWorkerConfig(dockerfile=docker_config_np)\n", - "docker_tag = \"openmined/custom-worker-np:latest\"\n", - "worker_pool_name = \"custom-worker-pool-numpy\"\n", - "request = ds_client.api.services.worker_pool.create_image_and_pool_request(\n", - " pool_name=worker_pool_name,\n", - " num_workers=1,\n", - " tag=docker_tag,\n", - " config=docker_config,\n", - " reason=\"I want to do some more cool data science with PySyft and Recordlinkage\",\n", - " registry_uid=test(),\n", - ")\n", - "assert isinstance(request, Request)\n", - "assert len(request.changes) == 2\n", - "assert request.changes[0].config == docker_config\n", - "assert request.changes[1].num_workers == 1\n", - "assert request.changes[1].pool_name == worker_pool_name" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "d7c69138-8275-4715-bf8d-90b95ba02dae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Approving request for domain syft-dev-node\n" - ] - } - ], - "source": [ - "# the domain client approve the request, so the image should be built\n", - "# and the worker pool should be launched\n", - "for r in domain_client.requests:\n", - " if r.id == request.id:\n", - " req_result = r.approve()\n", - " break\n", - "assert isinstance(req_result, SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "6433dcec-5ab0-4d6b-ace0-321aa66c5563", - "metadata": {}, - "outputs": [], - "source": [ - "launched_pool = ds_client.api.services.worker_pool.get_by_name(worker_pool_name)\n", - "assert isinstance(launched_pool, WorkerPool)\n", - "assert launched_pool.name == worker_pool_name\n", - "assert len(launched_pool.worker_list) == 1" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "03770eeb-06b1-4df6-ad1f-c4c9d1bb25eb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - "
\n", - "

custom-worker-pool-numpy

\n", - "

\n", - " Created on: \n", - " 2024-05-08 06:16:21\n", - "

\n", - "

\n", - " Healthy Workers:\n", - " 1 / 1\n", - "

\n", - "

\n", - " Running Workers:\n", - " 1 / 1\n", - "

\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "

SyftWorker List

\n", - "
\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - "\n", - "

0

\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "\n", - " " - ], - "text/markdown": [ - "```python\n", - "class WorkerPool:\n", - " id: str = f63d58bc20454e36ab6eb960a9dbc7e9\n", - " name: str = \"custom-worker-pool-numpy\"\n", - " image: str = syft.service.worker.worker_image.SyftWorkerImage\n", - " max_count: str = 1\n", - " workers: str = [syft.service.worker.worker_pool.SyftWorker]\n", - " created_at: str = 2024-05-08 06:16:21\n", - "\n", - "```" - ], - "text/plain": [ - "syft.service.worker.worker_pool.WorkerPool" - ] - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ds_client.api.services.worker_pool[2]" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "9b7efed7-ab8b-4151-8ece-da9b1d06c7cf", - "metadata": {}, - "outputs": [], - "source": [ - "worker: SyftWorker = launched_pool.workers[0]\n", - "assert launched_pool.name in worker.name\n", - "assert worker.status.value == \"Running\"\n", - "assert worker.healthcheck.value == \"βœ…\"\n", - "# assert worker.consumer_state.value == \"Idle\"\n", - "assert isinstance(worker.logs, str)\n", - "assert worker.job_id is None" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "0a8f92be-f6df-4874-a892-2bce4cf0308e", - "metadata": {}, - "outputs": [], - "source": [ - "built_image = ds_client.api.services.worker_image.get_by_config(docker_config)\n", - "assert isinstance(built_image, SyftWorkerImage)\n", - "assert built_image.id == launched_pool.image.id\n", - "assert worker.image.id == built_image.id" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "158d88fa-a3e3-4dfb-8758-2adfaa0015ec", - "metadata": {}, - "outputs": [], - "source": [ - "# third party\n", - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "id": "03a1ba9e-1449-41bc-989d-7c3a7beea09c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
SyftSuccess: Syft function 'custom_worker_func' successfully created. To add a code request, please create a project using `project = syft.Project(...)`, then use command `project.create_code_request`.

" - ], - "text/plain": [ - "SyftSuccess: Syft function 'custom_worker_func' successfully created. To add a code request, please create a project using `project = syft.Project(...)`, then use command `project.create_code_request`." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Dataset\n", - "data = np.array([1, 2, 3])\n", - "data_action_obj = sy.ActionObject.from_obj(data)\n", - "data_pointer = domain_client.api.services.action.set(data_action_obj)\n", - "\n", - "# Function\n", - "\n", - "\n", - "@sy.syft_function(\n", - " input_policy=sy.ExactMatch(x=data_pointer),\n", - " output_policy=sy.SingleExecutionExactOutput(),\n", - " worker_pool_name=launched_pool.name,\n", - ")\n", - "def custom_worker_func(x):\n", - " return {\"y\": x + 1}\n", - "\n", - "\n", - "assert custom_worker_func.worker_pool_name == launched_pool.name\n", - "# Request code execution" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "id": "ea53e10b-a9c4-4ccc-8091-bce269a4ce02", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Approving request for domain syft-dev-node\n" - ] - } - ], - "source": [ - "code_request = ds_client.code.request_code_execution(custom_worker_func)\n", - "assert isinstance(code_request, Request)\n", - "assert code_request.status.value == 0 # pending\n", - "for r in domain_client.requests:\n", - " if r.id == code_request.id:\n", - " code_req_result = r.approve(approve_nested=True)\n", - " break\n", - "assert isinstance(code_req_result, SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "id": "36bb45d1-fdc3-4dc9-a18e-3514a85ec37c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
SyftWarning: This is a placeholder object, the real data lives on a different node and is not synced.

" - ], - "text/plain": [ - "SyftWarning: This is a placeholder object, the real data lives on a different node and is not synced." - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "job = ds_client.code.custom_worker_func(x=data_pointer, blocking=False)\n", - "assert job.status.value == \"created\"\n", - "job.wait()\n", - "assert job.status.value == \"completed\"\n", - "\n", - "job = domain_client.jobs[-1]\n", - "assert job.job_worker_id == worker.id\n", - "\n", - "# Validate the result received from the syft function\n", - "result = job.wait().get()\n", - "result_matches = result[\"y\"] == data + 1\n", - "assert result_matches.all()" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "id": "22dbb1a7-483e-454c-8595-1df11d3bc26d", - "metadata": {}, - "outputs": [], - "source": [ - "# Delete the workers of the launched pools\n", - "for worker in launched_pool.workers:\n", - " res = domain_client.api.services.worker.delete(uid=worker.id, force=True)\n", - " assert isinstance(res, sy.SyftSuccess)\n", - "\n", - "# TODO: delete the launched pool" - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "id": "ae1e49c4-0589-405f-9c42-902fbfd9efbf", - "metadata": {}, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[60], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Clean the build images\u001b[39;00m\n\u001b[1;32m 3\u001b[0m delete_result \u001b[38;5;241m=\u001b[39m domain_client\u001b[38;5;241m.\u001b[39mapi\u001b[38;5;241m.\u001b[39mservices\u001b[38;5;241m.\u001b[39mworker_image\u001b[38;5;241m.\u001b[39mremove(uid\u001b[38;5;241m=\u001b[39mbuilt_image\u001b[38;5;241m.\u001b[39mid)\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(delete_result, sy\u001b[38;5;241m.\u001b[39mSyftSuccess)\n", - "\u001b[0;31mAssertionError\u001b[0m: " - ] - } - ], - "source": [ - "# Clean the build images\n", - "\n", - "delete_result = domain_client.api.services.worker_image.remove(uid=built_image.id)\n", - "assert isinstance(delete_result, sy.SyftSuccess)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23fcd0e5-a013-4e3c-9210-527d34456707", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 059e3abde0679b0812e194042e21ca0ff685a802 Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Wed, 15 May 2024 10:07:11 +0200 Subject: [PATCH 093/114] remove depricated localonly flag --- .github/workflows/pr-tests-stack-public.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests-stack-public.yml b/.github/workflows/pr-tests-stack-public.yml index cf8fbedabd0..46f71b40b3f 100644 --- a/.github/workflows/pr-tests-stack-public.yml +++ b/.github/workflows/pr-tests-stack-public.yml @@ -82,7 +82,7 @@ jobs: if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' uses: crazy-max/ghaction-chocolatey@v3 with: - args: list --localonly + args: list - name: Install git if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' From fd74356c40747f9141ec2505ea1059e169cf7049 Mon Sep 17 00:00:00 2001 From: eelcovdw Date: Wed, 15 May 2024 12:07:00 +0200 Subject: [PATCH 094/114] rename project.start, add deprecated decorator --- notebooks/api/0.8/01-submit-code.ipynb | 4 +-- .../api/0.8/06-multiple-code-requests.ipynb | 4 +-- .../data-owner/03-messages-and-requests.ipynb | 2 +- .../data-scientist/05-syft-functions.ipynb | 2 +- .../06-messaging-and-requests.ipynb | 2 +- .../model-auditing/colab/01-user-log.ipynb | 2 +- .../01-data-scientist-submit-code.ipynb | 2 +- .../01-reading-from-a-csv.ipynb | 2 +- ...lecting-data-finding-common-complain.ipynb | 2 +- ...orough-has-the-most-noise-complaints.ipynb | 2 +- ...-weekday-bike-most-groupby-aggregate.ipynb | 2 +- ...ing-dataframes-scraping-weather-data.ipynb | 2 +- ...rations-which-month-was-the-snowiest.ipynb | 2 +- .../07-cleaning-up-messy-data.ipynb | 2 +- .../08-how-to-deal-with-timestamps.ipynb | 2 +- packages/syft/src/syft/client/client.py | 2 +- packages/syft/src/syft/client/syncing.py | 8 ++---- .../syft/src/syft/service/project/project.py | 7 +++++ packages/syft/src/syft/util/decorators.py | 26 +++++++++++++++++++ .../syft/tests/syft/project/project_test.py | 6 ++--- 20 files changed, 56 insertions(+), 27 deletions(-) diff --git a/notebooks/api/0.8/01-submit-code.ipynb b/notebooks/api/0.8/01-submit-code.ipynb index ec11b60af9f..761d1a96e7a 100644 --- a/notebooks/api/0.8/01-submit-code.ipynb +++ b/notebooks/api/0.8/01-submit-code.ipynb @@ -482,7 +482,7 @@ "outputs": [], "source": [ "# Once we start the project, it will submit the project along with the code request to the Domain Server\n", - "project = new_project.start()\n", + "project = new_project.send()\n", "project" ] }, @@ -599,7 +599,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.10.13" }, "toc": { "base_numbering": 1, diff --git a/notebooks/api/0.8/06-multiple-code-requests.ipynb b/notebooks/api/0.8/06-multiple-code-requests.ipynb index 6e19bc6731c..4be948cc00b 100644 --- a/notebooks/api/0.8/06-multiple-code-requests.ipynb +++ b/notebooks/api/0.8/06-multiple-code-requests.ipynb @@ -250,7 +250,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "\n", "project" ] @@ -578,7 +578,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.10.13" }, "toc": { "base_numbering": 1, diff --git a/notebooks/tutorials/data-owner/03-messages-and-requests.ipynb b/notebooks/tutorials/data-owner/03-messages-and-requests.ipynb index 5a59e9724f0..8e7a1618425 100644 --- a/notebooks/tutorials/data-owner/03-messages-and-requests.ipynb +++ b/notebooks/tutorials/data-owner/03-messages-and-requests.ipynb @@ -200,7 +200,7 @@ "metadata": {}, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "project" ] }, diff --git a/notebooks/tutorials/data-scientist/05-syft-functions.ipynb b/notebooks/tutorials/data-scientist/05-syft-functions.ipynb index da524a933e1..cbee1755a3d 100644 --- a/notebooks/tutorials/data-scientist/05-syft-functions.ipynb +++ b/notebooks/tutorials/data-scientist/05-syft-functions.ipynb @@ -400,7 +400,7 @@ "metadata": {}, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "project" ] }, diff --git a/notebooks/tutorials/data-scientist/06-messaging-and-requests.ipynb b/notebooks/tutorials/data-scientist/06-messaging-and-requests.ipynb index 3fbe3bfc055..5d7ff62fa94 100644 --- a/notebooks/tutorials/data-scientist/06-messaging-and-requests.ipynb +++ b/notebooks/tutorials/data-scientist/06-messaging-and-requests.ipynb @@ -200,7 +200,7 @@ "metadata": {}, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "project" ] }, diff --git a/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb b/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb index f53c1374203..226ac4f4006 100644 --- a/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb +++ b/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb @@ -481,7 +481,7 @@ "metadata": {}, "outputs": [], "source": [ - "project = audit_project.start()\n", + "project = audit_project.send()\n", "project" ] }, diff --git a/notebooks/tutorials/model-training/01-data-scientist-submit-code.ipynb b/notebooks/tutorials/model-training/01-data-scientist-submit-code.ipynb index 4d245cd6f06..3314a8d70eb 100644 --- a/notebooks/tutorials/model-training/01-data-scientist-submit-code.ipynb +++ b/notebooks/tutorials/model-training/01-data-scientist-submit-code.ipynb @@ -492,7 +492,7 @@ "metadata": {}, "outputs": [], "source": [ - "project = new_project.start()" + "project = new_project.send()" ] }, { diff --git a/notebooks/tutorials/pandas-cookbook/01-reading-from-a-csv.ipynb b/notebooks/tutorials/pandas-cookbook/01-reading-from-a-csv.ipynb index 730391a5881..d5cdc94cc9d 100644 --- a/notebooks/tutorials/pandas-cookbook/01-reading-from-a-csv.ipynb +++ b/notebooks/tutorials/pandas-cookbook/01-reading-from-a-csv.ipynb @@ -554,7 +554,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/02-selecting-data-finding-common-complain.ipynb b/notebooks/tutorials/pandas-cookbook/02-selecting-data-finding-common-complain.ipynb index 28587a7e3d4..09e1e25b8dc 100644 --- a/notebooks/tutorials/pandas-cookbook/02-selecting-data-finding-common-complain.ipynb +++ b/notebooks/tutorials/pandas-cookbook/02-selecting-data-finding-common-complain.ipynb @@ -760,7 +760,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/03-which-borough-has-the-most-noise-complaints.ipynb b/notebooks/tutorials/pandas-cookbook/03-which-borough-has-the-most-noise-complaints.ipynb index 747f7c0f792..51443872eb7 100644 --- a/notebooks/tutorials/pandas-cookbook/03-which-borough-has-the-most-noise-complaints.ipynb +++ b/notebooks/tutorials/pandas-cookbook/03-which-borough-has-the-most-noise-complaints.ipynb @@ -874,7 +874,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/04-weekday-bike-most-groupby-aggregate.ipynb b/notebooks/tutorials/pandas-cookbook/04-weekday-bike-most-groupby-aggregate.ipynb index 278363f5e6d..29878fd826c 100644 --- a/notebooks/tutorials/pandas-cookbook/04-weekday-bike-most-groupby-aggregate.ipynb +++ b/notebooks/tutorials/pandas-cookbook/04-weekday-bike-most-groupby-aggregate.ipynb @@ -634,7 +634,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/05-combining-dataframes-scraping-weather-data.ipynb b/notebooks/tutorials/pandas-cookbook/05-combining-dataframes-scraping-weather-data.ipynb index 384b8e10701..9afc01da2ec 100644 --- a/notebooks/tutorials/pandas-cookbook/05-combining-dataframes-scraping-weather-data.ipynb +++ b/notebooks/tutorials/pandas-cookbook/05-combining-dataframes-scraping-weather-data.ipynb @@ -821,7 +821,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/06-string-operations-which-month-was-the-snowiest.ipynb b/notebooks/tutorials/pandas-cookbook/06-string-operations-which-month-was-the-snowiest.ipynb index 404bdc30026..3544f6b82f4 100644 --- a/notebooks/tutorials/pandas-cookbook/06-string-operations-which-month-was-the-snowiest.ipynb +++ b/notebooks/tutorials/pandas-cookbook/06-string-operations-which-month-was-the-snowiest.ipynb @@ -723,7 +723,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/07-cleaning-up-messy-data.ipynb b/notebooks/tutorials/pandas-cookbook/07-cleaning-up-messy-data.ipynb index c5a1887d04e..f64f8728793 100644 --- a/notebooks/tutorials/pandas-cookbook/07-cleaning-up-messy-data.ipynb +++ b/notebooks/tutorials/pandas-cookbook/07-cleaning-up-messy-data.ipynb @@ -778,7 +778,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/notebooks/tutorials/pandas-cookbook/08-how-to-deal-with-timestamps.ipynb b/notebooks/tutorials/pandas-cookbook/08-how-to-deal-with-timestamps.ipynb index 5bb016f1cae..6d1c11f3153 100644 --- a/notebooks/tutorials/pandas-cookbook/08-how-to-deal-with-timestamps.ipynb +++ b/notebooks/tutorials/pandas-cookbook/08-how-to-deal-with-timestamps.ipynb @@ -728,7 +728,7 @@ }, "outputs": [], "source": [ - "project = new_project.start()\n", + "project = new_project.send()\n", "assert isinstance(project, sy.service.project.project.Project)\n", "project" ] diff --git a/packages/syft/src/syft/client/client.py b/packages/syft/src/syft/client/client.py index 67508864a72..1a0fee04e31 100644 --- a/packages/syft/src/syft/client/client.py +++ b/packages/syft/src/syft/client/client.py @@ -546,7 +546,7 @@ def create_project( user_email_address=user_email_address, members=[self], ) - project = project_create.start() + project = project_create.send() return project # TODO: type of request should be REQUEST, but it will give circular import error diff --git a/packages/syft/src/syft/client/syncing.py b/packages/syft/src/syft/client/syncing.py index a48cef05ab3..371b77df22a 100644 --- a/packages/syft/src/syft/client/syncing.py +++ b/packages/syft/src/syft/client/syncing.py @@ -1,5 +1,4 @@ # stdlib -import warnings # relative from ..abstract_node import NodeSideType @@ -12,6 +11,7 @@ from ..service.sync.resolve_widget import ResolveWidget from ..service.sync.sync_state import SyncState from ..types.uid import UID +from ..util.decorators import deprecated from .client import SyftClient from .sync_decision import SyncDecision from .sync_decision import SyncDirection @@ -69,12 +69,8 @@ def resolve(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget: return widget +@deprecated(reason="resolve_single has been renamed to resolve", return_syfterror=True) def resolve_single(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget: - warnings.warn( - "resolve_single has been renamed to resolve", - DeprecationWarning, - stacklevel=1, - ) return resolve(obj_diff_batch) diff --git a/packages/syft/src/syft/service/project/project.py b/packages/syft/src/syft/service/project/project.py index aa8048f788e..d9b84ef9f15 100644 --- a/packages/syft/src/syft/service/project/project.py +++ b/packages/syft/src/syft/service/project/project.py @@ -38,6 +38,7 @@ from ...types.uid import UID from ...util import options from ...util.colors import SURFACE +from ...util.decorators import deprecated from ...util.markdown import markdown_as_class_with_fields from ...util.util import full_name_with_qualname from ..code.user_code import SubmitUserCode @@ -1261,7 +1262,13 @@ def create_code_request( reason=reason, ) + @deprecated( + reason="Project.start has been renamed to Project.send", return_syfterror=True + ) def start(self, return_all_projects: bool = False) -> Project | list[Project]: + return self.send(return_all_projects=return_all_projects) + + def send(self, return_all_projects: bool = False) -> Project | list[Project]: # Currently we are assuming that the first member is the leader # This would be changed in our future leaderless approach leader = self.clients[0] diff --git a/packages/syft/src/syft/util/decorators.py b/packages/syft/src/syft/util/decorators.py index 1262099d1c6..acfeba490e8 100644 --- a/packages/syft/src/syft/util/decorators.py +++ b/packages/syft/src/syft/util/decorators.py @@ -2,6 +2,10 @@ from collections.abc import Callable import functools from typing import Any +import warnings + +# relative +from ..service.response import SyftError def singleton(cls: Any) -> Callable: @@ -46,3 +50,25 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return previous_instances[cls].get("instance") return wrapper + + +def deprecated( + reason: str = "This function is deprecated and may be removed in the future.", + return_syfterror: bool = False, +) -> Callable: + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + def wrapper(*args: list, **kwargs: dict) -> Any: + message = f"{func.__qualname__} is deprecated: {reason}" + if return_syfterror: + return SyftError(message=message) + warnings.warn( + message, + category=DeprecationWarning, + stacklevel=2, + ) + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/packages/syft/tests/syft/project/project_test.py b/packages/syft/tests/syft/project/project_test.py index 9b2c8ce92f3..c186f2f35fa 100644 --- a/packages/syft/tests/syft/project/project_test.py +++ b/packages/syft/tests/syft/project/project_test.py @@ -23,7 +23,7 @@ def test_project_creation(worker): name="My Cool Project", description="My Cool Description", members=[ds_client] ) - project = new_project.start() + project = new_project.send() assert isinstance(project, Project) assert new_project.id == project.id @@ -47,7 +47,7 @@ def test_error_data_owner_project_creation(worker): name="My Cool Project", description="My Cool Description", members=[root_client] ) - project = new_project.start() + project = new_project.send() assert isinstance(project, sy.SyftError) assert project.message == "Only Data Scientists can create projects" @@ -96,7 +96,7 @@ def test_project_serde(worker): name="My Cool Project", description="My Cool Description", members=[root_client] ) - project = new_project.start() + project = new_project.send() ser_data = sy.serialize(project, to_bytes=True) assert isinstance(ser_data, bytes) From e34f6c75d455ff184244031f9e54c6e033565644 Mon Sep 17 00:00:00 2001 From: Yash Gorana Date: Wed, 15 May 2024 15:44:22 +0530 Subject: [PATCH 095/114] fix dockerignore paths --- .../backend/backend.dockerfile.dockerignore | 104 +++++++++--------- .../frontend/frontend.dockerfile.dockerignore | 15 ++- .../seaweedfs.dockerfile.dockerignore | 103 +++++++++-------- .../syft-client/syft.Dockerfile.dockerignore | 100 ++++++++--------- 4 files changed, 160 insertions(+), 162 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile.dockerignore b/packages/grid/backend/backend.dockerfile.dockerignore index c5bacaa51c3..2c06567a214 100644 --- a/packages/grid/backend/backend.dockerfile.dockerignore +++ b/packages/grid/backend/backend.dockerfile.dockerignore @@ -1,67 +1,63 @@ +# Paths should be against the docker root context dir i.e. /packages + # Syft -tests/ -*.md +**/tests/ +**/*.md # Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class +**/__pycache__/ +**/*.py[cod] +**/*$py.class # Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +**/.Python +**/build/ +**/develop-eggs/ +**/dist/ +**/downloads/ +**/eggs/ +**/.eggs/ +**/lib/ +**/lib64/ +**/parts/ +**/sdist/ +**/var/ +**/wheels/ +**/share/python-wheels/ +**/*.egg-info/ +**/.installed.cfg +**/*.egg +**/MANIFEST # Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py +**/.ipynb_checkpoints # Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +**/.env +**/.venv +**/env/ +**/venv/ +**/ENV/ +**/env.bak/ +**/venv.bak/ # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +**/htmlcov/ +**/.tox/ +**/.nox/ +**/.coverage +**/.coverage.* +**/.cache +**/nosetests.xml +**/coverage.xml +**/*.cover +**/*.py,cover +**/.hypothesis/ +**/.pytest_cache/ +**/cover/ + +# vim +**/*.swp # macOS -.DS_Store +**/.DS_Store diff --git a/packages/grid/frontend/frontend.dockerfile.dockerignore b/packages/grid/frontend/frontend.dockerfile.dockerignore index 90f9f7be934..449ac1c92ef 100644 --- a/packages/grid/frontend/frontend.dockerfile.dockerignore +++ b/packages/grid/frontend/frontend.dockerfile.dockerignore @@ -1,10 +1,15 @@ +# Paths should be relative to the context dir of this image i.e. /packages/grid/frontend/ + # Frontend -*.md +**/*.md # Dependency directories -node_modules -.svelte-kit -.pnpm-store +**/node_modules +**/.svelte-kit +**/.pnpm-store + +# vim +**/*.swp # macOS -.DS_Store +**/.DS_Store diff --git a/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore b/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore index 298280a5b63..98a48c5b17d 100644 --- a/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore +++ b/packages/grid/seaweedfs/seaweedfs.dockerfile.dockerignore @@ -1,66 +1,63 @@ +# Paths should be relative to the context dir of this image i.e. /packages/grid/seaweedfs/ + # SeaweedFS -*.md +**/tests/ +**/*.md # Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class +**/__pycache__/ +**/*.py[cod] +**/*$py.class # Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +**/.Python +**/build/ +**/develop-eggs/ +**/dist/ +**/downloads/ +**/eggs/ +**/.eggs/ +**/lib/ +**/lib64/ +**/parts/ +**/sdist/ +**/var/ +**/wheels/ +**/share/python-wheels/ +**/*.egg-info/ +**/.installed.cfg +**/*.egg +**/MANIFEST # Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py +**/.ipynb_checkpoints # Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +**/.env +**/.venv +**/env/ +**/venv/ +**/ENV/ +**/env.bak/ +**/venv.bak/ # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +**/htmlcov/ +**/.tox/ +**/.nox/ +**/.coverage +**/.coverage.* +**/.cache +**/nosetests.xml +**/coverage.xml +**/*.cover +**/*.py,cover +**/.hypothesis/ +**/.pytest_cache/ +**/cover/ + +# vim +**/*.swp # macOS -.DS_Store +**/.DS_Store diff --git a/packages/grid/syft-client/syft.Dockerfile.dockerignore b/packages/grid/syft-client/syft.Dockerfile.dockerignore index c5bacaa51c3..d78459cecbb 100644 --- a/packages/grid/syft-client/syft.Dockerfile.dockerignore +++ b/packages/grid/syft-client/syft.Dockerfile.dockerignore @@ -1,67 +1,67 @@ # Syft -tests/ -*.md +**/tests/ +**/*.md # Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class +**/__pycache__/ +**/*.py[cod] +**/*$py.class # Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +**/.Python +**/build/ +**/develop-eggs/ +**/dist/ +**/downloads/ +**/eggs/ +**/.eggs/ +**/lib/ +**/lib64/ +**/parts/ +**/sdist/ +**/var/ +**/wheels/ +**/share/python-wheels/ +**/*.egg-info/ +**/.installed.cfg +**/*.egg +**/MANIFEST # Jupyter Notebook -.ipynb_checkpoints +**/.ipynb_checkpoints # IPython -profile_default/ -ipython_config.py +**/profile_default/ +**/ipython_config.py # Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +**/.env +**/.venv +**/env/ +**/venv/ +**/ENV/ +**/env.bak/ +**/venv.bak/ # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ +**/htmlcov/ +**/.tox/ +**/.nox/ +**/.coverage +**/.coverage.* +**/.cache +**/nosetests.xml +**/coverage.xml +**/*.cover +**/*.py,cover +**/.hypothesis/ +**/.pytest_cache/ +**/cover/ # mypy -.mypy_cache/ -.dmypy.json -dmypy.json +**/.mypy_cache/ +**/.dmypy.json +**/dmypy.json # macOS -.DS_Store +**/.DS_Store From b01dec48936cf57245ea7df1f14f9383949bd92b Mon Sep 17 00:00:00 2001 From: Koen van der Veen Date: Wed, 15 May 2024 13:31:31 +0200 Subject: [PATCH 096/114] add constraining autocomplete using __syft_dir__ --- .../tutorials/hello-syft/01-hello-syft.ipynb | 5 +- packages/syft/src/syft/__init__.py | 89 ++++++++++++++----- packages/syft/src/syft/client/api.py | 17 +++- 3 files changed, 86 insertions(+), 25 deletions(-) diff --git a/notebooks/tutorials/hello-syft/01-hello-syft.ipynb b/notebooks/tutorials/hello-syft/01-hello-syft.ipynb index d773c3f6f0d..8a7f6a674d2 100644 --- a/notebooks/tutorials/hello-syft/01-hello-syft.ipynb +++ b/notebooks/tutorials/hello-syft/01-hello-syft.ipynb @@ -541,7 +541,8 @@ "autocompleter = get_ipython().Completer\n", "_, completions1 = autocompleter.complete(text=\"ds_client.code.\")\n", "_, completions2 = autocompleter.complete(text=\"ds_client.services.\")\n", - "_, completions3 = autocompleter.complete(text=\"ds_client.api.services.\")" + "_, completions3 = autocompleter.complete(text=\"ds_client.api.services.\")\n", + "_, completions4 = autocompleter.complete(text=\"ds_client.api.\")" ] }, { @@ -556,6 +557,8 @@ " \"ds_client.code.get_all\" in completions1,\n", " \"ds_client.services.code\" in completions2,\n", " \"ds_client.api.services.code\" in completions3,\n", + " \"ds_client.api.code\" in completions4,\n", + " \"ds_client.api.parse_raw\" not in completions4, # no pydantic completions on api\n", " ]\n", ")" ] diff --git a/packages/syft/src/syft/__init__.py b/packages/syft/src/syft/__init__.py index 1f6d1187ba3..f0dd9e427d5 100644 --- a/packages/syft/src/syft/__init__.py +++ b/packages/syft/src/syft/__init__.py @@ -5,6 +5,7 @@ import pathlib from pathlib import Path import sys +from types import MethodType from typing import Any # relative @@ -70,6 +71,7 @@ from .service.user.roles import Roles as roles # noqa: F401 from .service.user.user_service import UserService # noqa: F401 from .stable_version import LATEST_STABLE_SYFT +from .types.syft_object import SyftObject from .types.twin_object import TwinObject # noqa: F401 from .types.uid import UID # noqa: F401 from .util import filterwarnings # noqa: F401 @@ -120,30 +122,71 @@ def _patch_ipython_autocompletion() -> None: if ipython is None: return - ipython.Completer.evaluation = "limited" - ipython.Completer.use_jedi = False - policy = EVALUATION_POLICIES["limited"] - - # this allow for dynamic attribute getters for autocomplete - policy.allowed_getattr_external.update( - [ - ("syft.client.api", "APIModule"), - ("syft.client.api", "SyftAPI"), - ] - ) - original_can_get_attr = policy.can_get_attr - - def patched_can_get_attr(value: Any, attr: str) -> bool: - attr_name = "__syft_allow_autocomplete__" - - # first check if exist to prevent side effects - if hasattr(value, attr_name) and attr in getattr(value, attr_name, []): - return True - else: - return original_can_get_attr(value, attr) + try: + # this allows property getters to be used in nested autocomplete + ipython.Completer.evaluation = "limited" + ipython.Completer.use_jedi = False + policy = EVALUATION_POLICIES["limited"] + + policy.allowed_getattr_external.update( + [ + ("syft.client.api", "APIModule"), + ("syft.client.api", "SyftAPI"), + ] + ) + original_can_get_attr = policy.can_get_attr + + def patched_can_get_attr(value: Any, attr: str) -> bool: + attr_name = "__syft_allow_autocomplete__" + # first check if exist to prevent side effects + if hasattr(value, attr_name) and attr in getattr(value, attr_name, []): + if attr in dir(value): + return True + else: + return False + else: + return original_can_get_attr(value, attr) + + policy.can_get_attr = patched_can_get_attr + except Exception: + print("Failed to patch ipython autocompletion for syft property getters") - # this allows property getters to be used in nested autocomplete - policy.can_get_attr = patched_can_get_attr + try: + # this constraints the completions for autocomplete. + # if __syft_dir__ is defined we only autocomplete those properties + # stdlib + import re + + original_attr_matches = ipython.Completer.attr_matches + + def patched_attr_matches(self, text: str) -> list[str]: # type: ignore + res = original_attr_matches(text) + m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) + if not m2: + return res + expr, _ = m2.group(1, 2) + obj = self._evaluate_expr(expr) + if isinstance(obj, SyftObject) and hasattr(obj, "__syft_dir__"): + # here we filter all autocomplete results to only contain those + # defined in __syft_dir__, however the original autocomplete prefixes + # have the full path, while __syft_dir__ only defines the attr + attrs = set(obj.__syft_dir__()) + new_res = [] + for r in res: + splitted = r.split(".") + if len(splitted) > 1: + attr_name = splitted[-1] + if attr_name in attrs: + new_res.append(r) + return new_res + else: + return res + + ipython.Completer.attr_matches = MethodType( + patched_attr_matches, ipython.Completer + ) + except Exception: + print("Failed to patch syft autocompletion for __syft_dir__") _patch_ipython_autocompletion() diff --git a/packages/syft/src/syft/client/api.py b/packages/syft/src/syft/client/api.py index 3bc6c846f50..6890ba65c75 100644 --- a/packages/syft/src/syft/client/api.py +++ b/packages/syft/src/syft/client/api.py @@ -835,7 +835,22 @@ class SyftAPI(SyftObject): __syft_allow_autocomplete__ = ["services"] def __dir__(self) -> list[str]: - return ["services"] + modules = getattr(self.api_module, "_modules", []) + return ["services"] + modules + + def __syft_dir__(self) -> list[str]: + modules = getattr(self.api_module, "_modules", []) + return ["services"] + modules + + def __getattr__(self, name: str) -> Any: + try: + return getattr(self.api_module, name) + except Exception: + raise SyftAttributeError( + f"'SyftAPI' object has no submodule or method '{name}', " + "you may not have permission to access the module you are trying to access." + "If you think this is an error, try calling `client.refresh()` to update the API." + ) @staticmethod def for_user( From a613019290908ea5c9b049454f7ad7b52d196a64 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Wed, 15 May 2024 20:30:41 +0530 Subject: [PATCH 097/114] Fix CI by making the ansible uninstall conditional --- packages/grid/backend/backend.dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index f5c68b3fcb3..18b38e520fe 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -35,7 +35,7 @@ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ # remove torch because we already have the cpu version pre-installed sed --in-place /torch==/d ./syft/setup.cfg && \ uv pip install -e ./syft[data_science] && \ - uv pip freeze | grep ansible | xargs uv pip uninstall + if uv pip freeze | grep -q ansible; then uv pip freeze | grep ansible | xargs uv pip uninstall; fi # ==================== [Final] Setup Syft Server ==================== # From 8a511f70942e2eb4727e4d36722b52d6b5735607 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Wed, 15 May 2024 20:31:05 +0530 Subject: [PATCH 098/114] Remove deprecated orchestra launch args from tests --- tests/integration/conftest.py | 2 -- tests/integration/local/request_multiple_nodes_test.py | 2 -- tests/integration/local/syft_function_test.py | 1 - 3 files changed, 5 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index f6ccf94f32c..9152038b1f7 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -51,7 +51,6 @@ def full_low_worker(n_consumers: int = 3, create_producer: bool = True) -> Worke n_consumers=n_consumers, create_producer=create_producer, queue_port=None, - in_memory_workers=True, local_db=False, thread_workers=False, ) @@ -72,7 +71,6 @@ def full_high_worker(n_consumers: int = 3, create_producer: bool = True) -> Work n_consumers=n_consumers, create_producer=create_producer, queue_port=None, - in_memory_workers=True, local_db=False, thread_workers=False, ) diff --git a/tests/integration/local/request_multiple_nodes_test.py b/tests/integration/local/request_multiple_nodes_test.py index 601988673dc..e81f75b57d6 100644 --- a/tests/integration/local/request_multiple_nodes_test.py +++ b/tests/integration/local/request_multiple_nodes_test.py @@ -21,7 +21,6 @@ def node_1(): local_db=True, create_producer=True, n_consumers=1, - in_memory_workers=True, queue_port=None, ) yield node @@ -39,7 +38,6 @@ def node_2(): local_db=True, create_producer=True, n_consumers=1, - in_memory_workers=True, queue_port=None, ) yield node diff --git a/tests/integration/local/syft_function_test.py b/tests/integration/local/syft_function_test.py index 6ca60f3b90d..8cc85cce4e2 100644 --- a/tests/integration/local/syft_function_test.py +++ b/tests/integration/local/syft_function_test.py @@ -23,7 +23,6 @@ def node(): n_consumers=3, create_producer=True, queue_port=None, - in_memory_workers=True, local_db=False, ) # startup code here From dee12b1a0ef457574732a0780a87dfa50699e6d8 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 16 May 2024 09:05:30 +0530 Subject: [PATCH 099/114] remove ansible uninstallation from dockerfile --- packages/grid/backend/backend.dockerfile | 3 +-- packages/grid/syft-client/syft.Dockerfile | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/grid/backend/backend.dockerfile b/packages/grid/backend/backend.dockerfile index 18b38e520fe..08ea2c9a72a 100644 --- a/packages/grid/backend/backend.dockerfile +++ b/packages/grid/backend/backend.dockerfile @@ -34,8 +34,7 @@ COPY syft/src/syft/VERSION ./syft/src/syft/ RUN --mount=type=cache,target=/root/.cache,sharing=locked \ # remove torch because we already have the cpu version pre-installed sed --in-place /torch==/d ./syft/setup.cfg && \ - uv pip install -e ./syft[data_science] && \ - if uv pip freeze | grep -q ansible; then uv pip freeze | grep ansible | xargs uv pip uninstall; fi + uv pip install -e ./syft[data_science] # ==================== [Final] Setup Syft Server ==================== # diff --git a/packages/grid/syft-client/syft.Dockerfile b/packages/grid/syft-client/syft.Dockerfile index e3d1189a8e8..8f94e38b81b 100644 --- a/packages/grid/syft-client/syft.Dockerfile +++ b/packages/grid/syft-client/syft.Dockerfile @@ -14,8 +14,7 @@ RUN apk update && apk upgrade && \ COPY ./syft /tmp/syft RUN --mount=type=cache,target=/root/.cache,sharing=locked \ - pip install --user jupyterlab==4.1.6 pip-autoremove==0.10.0 /tmp/syft && \ - pip-autoremove ansible ansible-core -y + pip install --user jupyterlab==4.1.6 /tmp/syft # ==================== [Final] Setup Syft Client ==================== # From a9d198db795a2715187ee840f672acc00061eada Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Thu, 16 May 2024 09:14:53 +0530 Subject: [PATCH 100/114] remove additional references of hagrid in tox.ini --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 697354e11db..c79fa513cb1 100644 --- a/tox.ini +++ b/tox.ini @@ -470,7 +470,6 @@ description = Integration Tests for Syft Stack basepython = python3 deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} changedir = {toxinidir} passenv=HOME, USER allowlist_externals = @@ -501,8 +500,6 @@ description = Integration Tests for Core Stack using K8s basepython = python3 deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} - nbmake changedir = {toxinidir} passenv=HOME, USER, AZURE_BLOB_STORAGE_KEY allowlist_externals = From 21bd6b84b9afe0570787206d42e801da9a162844 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 14:33:19 +0530 Subject: [PATCH 101/114] modify deployments cells to markdown --- .../deploy/02-deploy-container.ipynb | 39 ++++++++++++------- .../tutorials/deploy/03-deploy-k8s-k3d.ipynb | 24 ++++++------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/notebooks/tutorials/deploy/02-deploy-container.ipynb b/notebooks/tutorials/deploy/02-deploy-container.ipynb index 962d5326721..be1c530ab2b 100644 --- a/notebooks/tutorials/deploy/02-deploy-container.ipynb +++ b/notebooks/tutorials/deploy/02-deploy-container.ipynb @@ -41,6 +41,17 @@ "You can execute the below command in your terminal to run the PySyft stack within a single docker container on port `8080`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Set Your Preferred Syft Version\n", + "\n", + "```sh\n", + "SYFT_VERSION=\"\"\n", + "```" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -54,7 +65,7 @@ " -e CREATE_PRODUCER=true \\\n", " -e INMEMORY_WORKERS=true \\\n", " -p 8080:80 --add-host=host.docker.internal:host-gateway \\\n", - " --name syft-example-domain-1 openmined/grid-backend:0.8.7-beta.7\n", + " --name syft-example-domain-1 openmined/grid-backend:$SYFT_VERSION\n", "```" ] }, @@ -68,22 +79,22 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ + "```python3\n", "# syft absolute\n", "import syft as sy\n", "\n", - "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")" + "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")\n", + "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This will return a node handle by connecting to `http://localhost:8080` which is the default host and port where your kubernetes cluster will be running. You can connect to a different host and port by setting the environment variables `NODE_URL` and `NODE_PORT`.\n", + "This will return a node handle by connecting to `http://localhost:8080` which is the default host and port where your docker container will be running. You can connect to a different host and port by setting the environment variables `NODE_URL` and `NODE_PORT`.\n", "```python\n", "import os\n", "\n", @@ -100,12 +111,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" + "```python3\n", + "client = node.login(email=\"info@openmined.org\", password=\"changethis\")\n", + "```" ] }, { @@ -116,13 +127,13 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ + "```python3\n", "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", - "client.upload_dataset(dataset)" + "client.upload_dataset(dataset)\n", + "```" ] }, { diff --git a/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb index 7453f6ee28d..c9cb2e1ebd8 100644 --- a/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb +++ b/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb @@ -87,15 +87,15 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ + "```python3\n", "# syft absolute\n", "import syft as sy\n", "\n", - "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")" + "node = sy.orchestra.launch(name=\"syft-example-domain-1\", deploy_to=\"remote\")\n", + "```" ] }, { @@ -119,12 +119,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" + "```python3\n", + "client = node.login(email=\"info@openmined.org\", password=\"changethis\")\n", + "```" ] }, { @@ -135,13 +135,13 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ + "```python3\n", "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", - "client.upload_dataset(dataset)" + "client.upload_dataset(dataset)\n", + "```" ] }, { From aaf8a800a63cd8b9ce3e314bc1041847fc39ef4d Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 14:45:58 +0530 Subject: [PATCH 102/114] change folder name from deploy to deployments --- .../tutorials/{deploy => deployments}/00-deployment-types.ipynb | 0 .../tutorials/{deploy => deployments}/01-deploy-python.ipynb | 0 .../tutorials/{deploy => deployments}/02-deploy-container.ipynb | 0 .../tutorials/{deploy => deployments}/03-deploy-k8s-k3d.ipynb | 0 .../tutorials/{deploy => deployments}/04-deploy-k8s-azure.ipynb | 0 .../tutorials/{deploy => deployments}/05-deploy-k8s-gcp.ipynb | 0 .../tutorials/{deploy => deployments}/06-deploy-k8s-aws.ipynb | 0 .../tutorials/{deploy => deployments}/07-deploy-devspace.ipynb | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename notebooks/tutorials/{deploy => deployments}/00-deployment-types.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/01-deploy-python.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/02-deploy-container.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/03-deploy-k8s-k3d.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/04-deploy-k8s-azure.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/05-deploy-k8s-gcp.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/06-deploy-k8s-aws.ipynb (100%) rename notebooks/tutorials/{deploy => deployments}/07-deploy-devspace.ipynb (100%) diff --git a/notebooks/tutorials/deploy/00-deployment-types.ipynb b/notebooks/tutorials/deployments/00-deployment-types.ipynb similarity index 100% rename from notebooks/tutorials/deploy/00-deployment-types.ipynb rename to notebooks/tutorials/deployments/00-deployment-types.ipynb diff --git a/notebooks/tutorials/deploy/01-deploy-python.ipynb b/notebooks/tutorials/deployments/01-deploy-python.ipynb similarity index 100% rename from notebooks/tutorials/deploy/01-deploy-python.ipynb rename to notebooks/tutorials/deployments/01-deploy-python.ipynb diff --git a/notebooks/tutorials/deploy/02-deploy-container.ipynb b/notebooks/tutorials/deployments/02-deploy-container.ipynb similarity index 100% rename from notebooks/tutorials/deploy/02-deploy-container.ipynb rename to notebooks/tutorials/deployments/02-deploy-container.ipynb diff --git a/notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb b/notebooks/tutorials/deployments/03-deploy-k8s-k3d.ipynb similarity index 100% rename from notebooks/tutorials/deploy/03-deploy-k8s-k3d.ipynb rename to notebooks/tutorials/deployments/03-deploy-k8s-k3d.ipynb diff --git a/notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb b/notebooks/tutorials/deployments/04-deploy-k8s-azure.ipynb similarity index 100% rename from notebooks/tutorials/deploy/04-deploy-k8s-azure.ipynb rename to notebooks/tutorials/deployments/04-deploy-k8s-azure.ipynb diff --git a/notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb b/notebooks/tutorials/deployments/05-deploy-k8s-gcp.ipynb similarity index 100% rename from notebooks/tutorials/deploy/05-deploy-k8s-gcp.ipynb rename to notebooks/tutorials/deployments/05-deploy-k8s-gcp.ipynb diff --git a/notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb b/notebooks/tutorials/deployments/06-deploy-k8s-aws.ipynb similarity index 100% rename from notebooks/tutorials/deploy/06-deploy-k8s-aws.ipynb rename to notebooks/tutorials/deployments/06-deploy-k8s-aws.ipynb diff --git a/notebooks/tutorials/deploy/07-deploy-devspace.ipynb b/notebooks/tutorials/deployments/07-deploy-devspace.ipynb similarity index 100% rename from notebooks/tutorials/deploy/07-deploy-devspace.ipynb rename to notebooks/tutorials/deployments/07-deploy-devspace.ipynb From 9e472d10fdade976f34290c43d383e7050846364 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 14:46:24 +0530 Subject: [PATCH 103/114] delete old data-engineer folder --- .../01-setting-up-dev-mode.ipynb | 370 ----------------- .../data-engineer/02-deployment-types.ipynb | 378 ------------------ .../tutorials/data-engineer/03-hagrid.ipynb | 73 ---- .../data-engineer/04-deploy-container.ipynb | 107 ----- .../data-engineer/05-deploy-stack.ipynb | 81 ---- .../data-engineer/06-deploy-to-azure.ipynb | 114 ------ .../data-engineer/07-deploy-to-gcp.ipynb | 73 ---- .../data-engineer/08-deploy-to-aws.ipynb | 152 ------- .../data-engineer/09-deploying-enclave.ipynb | 41 -- .../data-engineer/10-custom-deployment.ipynb | 97 ----- ...11-installing-and-upgrading-via-helm.ipynb | 364 ----------------- 11 files changed, 1850 deletions(-) delete mode 100644 notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb delete mode 100644 notebooks/tutorials/data-engineer/02-deployment-types.ipynb delete mode 100644 notebooks/tutorials/data-engineer/03-hagrid.ipynb delete mode 100644 notebooks/tutorials/data-engineer/04-deploy-container.ipynb delete mode 100644 notebooks/tutorials/data-engineer/05-deploy-stack.ipynb delete mode 100644 notebooks/tutorials/data-engineer/06-deploy-to-azure.ipynb delete mode 100644 notebooks/tutorials/data-engineer/07-deploy-to-gcp.ipynb delete mode 100644 notebooks/tutorials/data-engineer/08-deploy-to-aws.ipynb delete mode 100644 notebooks/tutorials/data-engineer/09-deploying-enclave.ipynb delete mode 100644 notebooks/tutorials/data-engineer/10-custom-deployment.ipynb delete mode 100644 notebooks/tutorials/data-engineer/11-installing-and-upgrading-via-helm.ipynb diff --git a/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb b/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb deleted file mode 100644 index ed84817235f..00000000000 --- a/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb +++ /dev/null @@ -1,370 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Setting up Dev Mode" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "If you would like to work on the PySyft codebase, you can set up PySyft in dev mode. You will need to clone the repository, install syft locally and run the code you installed" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Cloning the Repo" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "First, we start by cloning the repo" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "If you have an SSH key enabled in your github account, use" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "`git clone git@github.com:OpenMined/PySyft.git`" - ] - }, - { - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "Otherwise use" - ] - }, - { - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "`git clone https://github.com/OpenMined/PySyft.git`" - ] - }, - { - "cell_type": "markdown", - "id": "8", - "metadata": {}, - "source": [ - "## Installing Syft" - ] - }, - { - "cell_type": "markdown", - "id": "9", - "metadata": {}, - "source": [ - "To install Syft `cd` into the directory in which you cloned PySyft and type\n", - "\n", - "```bash\n", - "pip install -e packages/syft\n", - "```\n", - "\n", - "This installs `syft` in editable mode, such any change in code are reflected in your environment." - ] - }, - { - "cell_type": "markdown", - "id": "10", - "metadata": {}, - "source": [ - "## Running Tox Tests" - ] - }, - { - "cell_type": "markdown", - "id": "11", - "metadata": {}, - "source": [ - "[Tox](https://tox.wiki/en/latest/) is a project that \"aims to automate and standardize testing in Python\". For PySyft development, it is used to simplify testing and setting up several environment in a way that works for every developer working on PySyft. You can list the commands that you can execute using `tox-l`, which will give a result similar to this" - ] - }, - { - "cell_type": "markdown", - "id": "12", - "metadata": {}, - "source": [ - "```\n", - "> tox -l\n", - "\n", - "hagrid.publish\n", - "lint\n", - "stack.test.integration\n", - "syft.docs\n", - "syft.jupyter\n", - "syft.publish\n", - "syft.test.security\n", - "syft.test.unit\n", - "syft.test.notebook\n", - "stack.test.notebook\n", - "stack.test.vm\n", - "frontend.test.unit\n", - "frontend.test.e2e\n", - "frontend.generate.types\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "13", - "metadata": {}, - "source": [ - "This shows us the list of environments that are specified for PySyft. To see what these environments do, have a look at the `tox.ini` file in the main PySyft repo." - ] - }, - { - "cell_type": "markdown", - "id": "14", - "metadata": {}, - "source": [ - "You can run an environment using `tox -e `. For instance, to run the unit tests, run" - ] - }, - { - "cell_type": "markdown", - "id": "15", - "metadata": {}, - "source": [ - "```\n", - "tox -e syft.test.unit\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "16", - "metadata": {}, - "source": [ - "This tox environment is relatively simple, and just uses pytest to run all the tests for the syft packages. However, some environments are more complicated, and run a series of commands that start multiple processes, docker containers and set up a lot of infrastructure before running the tests. The good thing is that with tox, you dont need to worry about that, you can just run the commands." - ] - }, - { - "cell_type": "markdown", - "id": "17", - "metadata": {}, - "source": [ - "## Using Jupyter Environment" - ] - }, - { - "cell_type": "markdown", - "id": "18", - "metadata": {}, - "source": [ - "Pysyft has a tox command to set up a local jupyter notebook environment, which is useful for development." - ] - }, - { - "cell_type": "markdown", - "id": "19", - "metadata": {}, - "source": [ - "```\n", - "tox -e syft.jupyter\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "20", - "metadata": {}, - "source": [ - "PySyft makes extensive use of jupyter notebook, and a lot of developers use it for experiments when writing code. It can be useful to setup a local gitignore (only for you, not pushed to git) to have a playground where you can experiment, without needing to push files to git, or change the .gitignore. You can do this by adding a folder to your `.git/info/exclude` file, which works similar to the `.gitignore` file, e.g. if we add\n", - "```\n", - "notebooks/experimental/* \n", - "```\n", - "to `.git/info/exclude`, git wont sync the changes to the `experimental` folder to github\n", - "\n", - "`Note:` For developers in MS Windows, before development make sure that your development path does not contain any white spaces in between.\n", - "\n", - "Example:\n", - " \n", - "**Invalid Path:** `D:/test space/new env/openmined/PySyft`\n", - "\n", - "**Valid Path:** `D:/test-space/new_env/openmined/PySyft`\n", - "\n", - "The issue with paths containing spaces causing problems on Windows is due to the way that Windows handles file paths, but as long as the development path is free of white spaces, you are good to go. This is not a specific issue related to PySyft." - ] - }, - { - "cell_type": "markdown", - "id": "21", - "metadata": {}, - "source": [ - "## Working with Python Domain" - ] - }, - { - "cell_type": "markdown", - "id": "22", - "metadata": {}, - "source": [ - "PySyft enables a network of computers to connect to each other and do privacy preserving data analysis. The Nodes in the network that hold some data are called `Domains`. When we develop with PySyft, it is very common to start a domain as the first step. `PySyft` makes it very easy to develop against a domain in a notebook by providing an interface (`sy.orchestra`) that allows you to start a domain with a webserver in a notebook in the background, which is a lightweight version of a Domain that would be used in production. You can specify options such as what kind of database you are using, whether you want to use networking and how many processes you want to use. You can launch a Domain by simply executing:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23", - "metadata": {}, - "outputs": [], - "source": [ - "# syft absolute\n", - "import syft as sy" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "24", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "node = sy.orchestra.launch(\n", - " name=\"dev-mode-example-domain-1\", port=8020, reset=True, dev_mode=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "If we dont need a webserver (for development this is true in many cases), we can omit the port and use. \n", - "```\n", - "node = sy.orchestra.launch(name=\"dev-mode-example-domain-1\", dev_mode=True, reset=True)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "26", - "metadata": {}, - "source": [ - "**One of the benefits of not using a port is that you can use a debugger and set breakpoints within api calls. This makes debugging way faster in many cases**" - ] - }, - { - "cell_type": "markdown", - "id": "27", - "metadata": {}, - "source": [ - "Now, we are ready to start using the domain. The domain comes with standard login credentials for the admin (just for development)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28", - "metadata": {}, - "outputs": [], - "source": [ - "client = node.login(email=\"info@openmined.org\", password=\"changethis\")" - ] - }, - { - "cell_type": "markdown", - "id": "29", - "metadata": {}, - "source": [ - "Once you are logged in, you are ready to start using the domain, for instance for creating a dataset (this one is empty, just as a example)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = sy.Dataset(name=\"my dataset\", asset_list=[])\n", - "client.upload_dataset(dataset)" - ] - }, - { - "cell_type": "markdown", - "id": "31", - "metadata": {}, - "source": [ - "Lastly to stop or terminate your Domain, we can execute the following command:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32", - "metadata": {}, - "outputs": [], - "source": [ - "node.land()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.16" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": true - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/02-deployment-types.ipynb b/notebooks/tutorials/data-engineer/02-deployment-types.ipynb deleted file mode 100644 index 1bd572a26fe..00000000000 --- a/notebooks/tutorials/data-engineer/02-deployment-types.ipynb +++ /dev/null @@ -1,378 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deployment Types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1", - "metadata": {}, - "outputs": [], - "source": [ - "# syft absolute\n", - "import syft as sy" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Dev Python Domain\n" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "Syft supports creating a Python domain in editable mode.\n", - "This is used mainly for experimental and development purposes.\n", - "In __Dev Python Domain__ the domain instance runs locally using the SQLite as the main storage.\n", - "This enables faster development and requires less recources to operate.\n", - "\n", - "The __Dev Python Domain__ supports two options:\n", - "1. Memory node - full `syft` functionality __locally__, SQLite as a local storage.\n", - "2. Webserver node - full `syft` functionality with API \n", - "\n", - "__When you need this?__
\n", - "_When you want to develop Syft or try-out new funcitonality from separate branch._\n", - "\n", - "__Prerequistes:__
\n", - "1. Syft repository pulled from Github - [github.com/OpenMined/PySyft](https://github.com/OpenMined/PySyft)\n", - "\n", - "For broader explanation refer to the notebook [01-setting-dev-mode.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/01-setting-up-dev-mode.ipynb)\n", - "\n", - "To launch the local __Dev Python Domain__ use the following steps:" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "#### 1.1 Launch Dev Memory Node" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5", - "metadata": {}, - "outputs": [], - "source": [ - "memory_node = sy.orchestra.launch(\n", - " name=\"Arbitrary Dev Node\",\n", - " dev_mode=True,\n", - " reset=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6", - "metadata": {}, - "outputs": [], - "source": [ - "assert memory_node is not None" - ] - }, - { - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "#### 1.2 Launch Dev Webserver Node" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8", - "metadata": {}, - "outputs": [], - "source": [ - "webserver_node = sy.orchestra.launch(\n", - " name=\"Arbitrary Webserver Dev Node\", dev_mode=True, reset=True, port=8081\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9", - "metadata": {}, - "outputs": [], - "source": [ - "assert webserver_node is not None" - ] - }, - { - "cell_type": "markdown", - "id": "10", - "metadata": {}, - "source": [ - "#### 2. Login Into Nodes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11", - "metadata": {}, - "outputs": [], - "source": [ - "memory_node_client = memory_node.login(\n", - " email=\"info@openmined.org\", password=\"changethis\"\n", - ")\n", - "memory_node_client" - ] - }, - { - "cell_type": "markdown", - "id": "12", - "metadata": {}, - "source": [ - "#### 3. Landing Memory and Webserver Node" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13", - "metadata": {}, - "outputs": [], - "source": [ - "memory_node.land()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14", - "metadata": {}, - "outputs": [], - "source": [ - "webserver_node.land()" - ] - }, - { - "cell_type": "markdown", - "id": "15", - "metadata": {}, - "source": [ - "----" - ] - }, - { - "cell_type": "markdown", - "id": "16", - "metadata": {}, - "source": [ - "## Single Container / Enclave (TBD)" - ] - }, - { - "cell_type": "markdown", - "id": "17", - "metadata": {}, - "source": [ - "Single Container deployment is used when fast and painless deployment of `syft` with all essential functionality is needed. This deployment type contains the `syft` and SQLite as a light-weight database in a single container.\n", - "\n", - "__When you need this?__
\n", - "_When you quickly want to test syft in a single container._\n", - "\n", - "__Prerequistes:__
\n", - "1. Syft repository pulled from Github - [github.com/OpenMined/PySyft](https://github.com/OpenMined/PySyft)\n", - "1. Docker Installed - [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)\n" - ] - }, - { - "cell_type": "markdown", - "id": "18", - "metadata": {}, - "source": [ - "#### Deploy Syft in Single Container Mode" - ] - }, - { - "cell_type": "markdown", - "id": "19", - "metadata": {}, - "source": [ - "Enter the PySyft Repository and run the following command\n", - "\n", - "`docker run -it -e DEFAULT_ROOT_PASSWORD=secret -e PORT=8080 -p 8080:8080 openmined/grid-enclave:0.8.1`\n", - "\n", - "----" - ] - }, - { - "cell_type": "markdown", - "id": "20", - "metadata": {}, - "source": [ - "## Full Container Stack" - ] - }, - { - "cell_type": "markdown", - "id": "21", - "metadata": {}, - "source": [ - "Syft can operate as a container stack. This setting consider deployment of following containers:\n", - " - Backend - contains `Syft` and corresponding logic to execute code in _sync_ manner\n", - " - Backend Stream - contains `Syft` and logic to queue message in RabbitMQ\n", - " - Celery Worker - contains `Syft` and logic to execute message received from RabbitMQ\n", - " - RabbitMQ - receives messages from Backend Stream and passes them into Celery Worker\n", - " - Redis - each `syft` object has a `UUID`, and stored in Redis as a `key`/`value` pair\n", - " - Mongo - Stores non-private metadata that are related to `grid` operation, such as __RBAC__ or `BLOB`s metadata \n", - " - SeaweedFS - Stores the `BLOB`s, compatible with Amazon S3 protocols\n", - " - Jaeger - distributed end-to-end tracing\n", - "\n", - "__When you need this?__
\n", - "_When you need a Syft domain/gateway node locally._\n", - "\n", - "__Prerequistes:__
\n", - "1. Syft installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "1. Hagrid installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "1. Docker Installed - [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)\n", - "\n", - "\n", - "Easiest way to launch the Full Container Stack is the `hagrid` cli tool.\n", - "\n", - "Basic syntax of Hagrdi deployment command is the following:
\n", - "> `hagrid launch to :`\n", - "\n", - "To deploy the full container stack use the following command:
\n", - "\n", - "> `hagrid launch test_domain domain to docker:8081`\n", - "\n", - "For detailed explanation of Full Container Stack deployment refer to the notebook [05-deploy-stack.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/05-deploy-stack.ipynb)" - ] - }, - { - "cell_type": "markdown", - "id": "22", - "metadata": {}, - "source": [ - "----" - ] - }, - { - "cell_type": "markdown", - "id": "23", - "metadata": {}, - "source": [ - "## VM Container Host" - ] - }, - { - "cell_type": "markdown", - "id": "24", - "metadata": {}, - "source": [ - "Ability to easily deploy `syft` stack to __anywhere__. By anywhere we mean an existing linux server accessible via `ssh` connection. `hagrid` cli tool can do all the hard work for us, by defining the desired system state using `ansible` and deploying all containers (defined in the previous section).\n", - "\n", - "__When you need this?__
\n", - "_When you need to deploy Syft domain/gateway node on a remote host, whether Virtual Machine or real Linux server._\n", - "\n", - "__Prerequistes:__
\n", - "1. Syft installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "2. Hagrid installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "3. VM accessible via SSH\n", - "\n", - "Deploy Syft `domain`/`network` node to the remote VM using following command:\n", - "\n", - "> `hagrid launch test_domain domain to 100.0.0.1 --username=ubuntu --auth-type=key --key-path=~/.ssh/hagrid_ssh_key`\n", - "\n", - "All flags marked with `--` are optional, if not provided `hagrid` will interactively ask you to provide all necessary details. More details on `hagrid` usage can be found in following notebook [03-hagrid.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/03-hagrid.ipynb)\n", - "\n", - "If you want to deploy to Cloud providers reffer to corresponding notebook:\n", - "- Azure - [06-deploy-to-azure.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/06-deploy-to-azure.ipynb)\n", - "- GCP - [07-deploy-to-gcp.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/07-deploy-to-gcp.ipynb)\n", - "- AWS - [08-deploy-to-aws.ipynb](https://github.com/OpenMined/PySyft/blob/dev/notebooks/tutorials/data-engineer/08-deploy-to-aws.ipynb)\n", - "\n", - ">__Note__: VM Container Host supports deployment _only from Linux or MacOS_ machines, since it requires `ansible`
that is not supported by Windows \n" - ] - }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "----" - ] - }, - { - "cell_type": "markdown", - "id": "26", - "metadata": {}, - "source": [ - "## Gateway Nodes" - ] - }, - { - "cell_type": "markdown", - "id": "27", - "metadata": {}, - "source": [ - "Gateway Nodes are used to interconnect multiple `domain` nodes.\n", - "Essentially, `gateway` nodes use the same containers and code, although with different configurations.\n", - "`gateway` nodes do not have the Frontend and Blob storage. \n", - "\n", - "__When you need this?__
\n", - "_When you need to interconnect two or more domain nodes._\n", - "\n", - "__Prerequistes:__
\n", - "1. Syft installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "1. Hagrid installed - [pypi.org/project/syft](https://pypi.org/project/syft/)\n", - "1. Docker installed or SSH connection to VM\n", - "\n", - "The `hagrid` cli can be used to deploy the `gateway` nodes, as a local container stack deployment or remote VM host deployment.\n", - "\n", - "To deploy `gateway` node us the following command:
\n", - "> `hagrid launch gateway to :`\n", - "\n", - "Example of launching the `gateway` node called `test-gateway`:
\n", - "> `hagrid launch test-gateway gateway to docker:9082`\n" - ] - }, - { - "cell_type": "markdown", - "id": "28", - "metadata": {}, - "source": [ - "----" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/03-hagrid.ipynb b/notebooks/tutorials/data-engineer/03-hagrid.ipynb deleted file mode 100644 index 3ad7cf9c25d..00000000000 --- a/notebooks/tutorials/data-engineer/03-hagrid.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Installing HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Python PATH" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Debugging HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "## Ansible and Windows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/04-deploy-container.ipynb b/notebooks/tutorials/data-engineer/04-deploy-container.ipynb deleted file mode 100644 index dd016d74ae5..00000000000 --- a/notebooks/tutorials/data-engineer/04-deploy-container.ipynb +++ /dev/null @@ -1,107 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploying a Container" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Docker 1-liner" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "```\n", - "$ docker run -it -e DEFAULT_ROOT_PASSWORD=secret -e PORT=8080 -p 8080:8080 openmined/grid-enclave:0.8.2.b0\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Azure CLI" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "$ az group create --name test-container --location eastus" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "$ az container create --resource-group test-container --name syft --image openmined/grid-enclave:0.8.2.b0 --dns-name-label syft-demo --ports 80 --environment-variables PORT=80 DEFAULT_ROOT_PASSWORD=secret" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "## From HAGrid" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "## Volume Mounts" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/05-deploy-stack.ipynb b/notebooks/tutorials/data-engineer/05-deploy-stack.ipynb deleted file mode 100644 index 2ac0fcc7dff..00000000000 --- a/notebooks/tutorials/data-engineer/05-deploy-stack.ipynb +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploy the Stack" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Docker Compose" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## HAGrid" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Build Source" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "## Volume Mounts" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "## Docker Networks" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/06-deploy-to-azure.ipynb b/notebooks/tutorials/data-engineer/06-deploy-to-azure.ipynb deleted file mode 100644 index 397d3f1016b..00000000000 --- a/notebooks/tutorials/data-engineer/06-deploy-to-azure.ipynb +++ /dev/null @@ -1,114 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploy to Azure" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Installing CLI Tool" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Authorizing CLI Tool" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Deploying a Single Container" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "$ az group create --name test-container --location eastus" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "$ az container create --resource-group test-container --name syft --image openmined/grid-enclave:0.8.2.b0 --dns-name-label syft-demo --ports 80 --environment-variables PORT=80 DEFAULT_ROOT_PASSWORD=secret" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "## Deploying a Domain" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "## Checking Firewall Rules" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "8", - "metadata": {}, - "source": [ - "## Logging in via SSH" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/07-deploy-to-gcp.ipynb b/notebooks/tutorials/data-engineer/07-deploy-to-gcp.ipynb deleted file mode 100644 index 827f1d5e129..00000000000 --- a/notebooks/tutorials/data-engineer/07-deploy-to-gcp.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploy to Google Cloud Platform (GCP)" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Installing CLI Tool" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "## Authorizing CLI Tool" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Deploying a Domain" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "## Checking Firewall Rules" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "## Logging in via SSH" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/08-deploy-to-aws.ipynb b/notebooks/tutorials/data-engineer/08-deploy-to-aws.ipynb deleted file mode 100644 index 7b8a28ec777..00000000000 --- a/notebooks/tutorials/data-engineer/08-deploy-to-aws.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploy to AWS" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Installing CLI Tool" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "Please refer to the docs for installing the AWS CLI tool: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html. It has instructions for the different operating systems such as Mac, Windows and Linux" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Authorizing CLI Tool" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "Please go through this for setting up the CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html. \n", - "\n", - "A common/quick way is to use to authenticate using IAM user credentials. Please refer to this doc for the steps involved: https://docs.aws.amazon.com/cli/latest/userguide/cli-authentication-user.html" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "## Deploying a Domain" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "Use `hagrid launch {domain_name} domain to aws [--no-provision]` command to launch your domain to an AWS EC2 instance. The --no-provision flag is optional and can be used if you do not want to provision all the resources using ansible (If you're not familiar with this, just ignore this flag) " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "You would be prompted with a series of questions.\n", - "\n", - "Please specify the region where you want your EC2 instance to be deployed.\n", - "\n", - "Please specify a name for the security group to be created. A security group is used to control the inbound and outbound traffic to/from the EC2 instance. Please check https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html for more information.\n", - "Then specify the IP addresses to be white-listed for incoming traffic to the EC2 instance. Please ensure that you enter it in CIDR notation. The default is 0.0.0.0/0 which means that all inbound traffic is allowed.\n", - "On these IP addresses, we open the following ports: 80, 443, 22.\n", - "\n", - "Then, please specify the EC2 instance type. By default, it is t2.xlarge.\n", - "\n", - "We need an EC2 key pair in order to SSH into the instance. If you already have a key-pair, please specify the name and the path where it is stored. Otherwise, if you do not have one, we will create one with the given name and store it in the path you specify. (Note: creating a keypair might not work properly with windows powershell).\n", - "\n", - "\n", - "Then specify the repo and branch for the source code. You can leave it as the default.\n", - "\n", - "\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "8", - "metadata": {}, - "source": [ - "## Checking Firewall Rules" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "9", - "metadata": {}, - "source": [ - "You could go to the AWS console, and navigate to the region where you deployed your instance. Search for EC2 and go over to the Security Groups tab (or directly search for Security Group). In the list of security groups, identify the one you created using the name. If you go inside, you would see the inbound and outbound rules." - ] - }, - { - "cell_type": "markdown", - "id": "10", - "metadata": {}, - "source": [ - "## Logging in via SSH" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "11", - "metadata": {}, - "source": [ - "Please refer to the steps in the doc to connect to your EC2 instance using SSH: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html" - ] - }, - { - "cell_type": "markdown", - "id": "12", - "metadata": {}, - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/09-deploying-enclave.ipynb b/notebooks/tutorials/data-engineer/09-deploying-enclave.ipynb deleted file mode 100644 index 11c0fba438e..00000000000 --- a/notebooks/tutorials/data-engineer/09-deploying-enclave.ipynb +++ /dev/null @@ -1,41 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Deploying an Enclave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/10-custom-deployment.ipynb b/notebooks/tutorials/data-engineer/10-custom-deployment.ipynb deleted file mode 100644 index 11b2f707b35..00000000000 --- a/notebooks/tutorials/data-engineer/10-custom-deployment.ipynb +++ /dev/null @@ -1,97 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Custom Deployment" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## What you need" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "### Container Engine" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "### File Mounts" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "### Network Access" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "### Python Client" - ] - }, - { - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "### Red Hat and Podman" - ] - }, - { - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "### Kubernetes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/data-engineer/11-installing-and-upgrading-via-helm.ipynb b/notebooks/tutorials/data-engineer/11-installing-and-upgrading-via-helm.ipynb deleted file mode 100644 index 4775672f760..00000000000 --- a/notebooks/tutorials/data-engineer/11-installing-and-upgrading-via-helm.ipynb +++ /dev/null @@ -1,364 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0", - "metadata": {}, - "source": [ - "# Installing using Helm" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "## Add Helm Repo" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "```bash\n", - "helm repo add openmined https://openmined.github.io/PySyft/helm\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "3", - "metadata": {}, - "source": [ - "## Update Repo" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "```bash\n", - "helm repo update openmined\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "## Search for available Chart versions" - ] - }, - { - "cell_type": "markdown", - "id": "6", - "metadata": {}, - "source": [ - "### Search for available versionsΒΆ" - ] - }, - { - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "```bash\n", - "helm search repo openmined/syft --versions --devel\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "8", - "metadata": {}, - "source": [ - "### Set the version to install" - ] - }, - { - "cell_type": "markdown", - "id": "9", - "metadata": {}, - "source": [ - "```bash\n", - "export SYFT_VERSION=\"\"\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "10", - "metadata": {}, - "source": [ - "## Setup a registry" - ] - }, - { - "cell_type": "markdown", - "id": "11", - "metadata": {}, - "source": [ - "One needs to setup a registry either locally or on the cloud. To set one up locally, one can follow the following commands." - ] - }, - { - "cell_type": "markdown", - "id": "12", - "metadata": {}, - "source": [ - "```bash\n", - "k3d registry create registry.localhost --port 12345 -v `pwd`/k3d-registry:/var/lib/registry || true\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "13", - "metadata": {}, - "source": [ - "Setup a load balancer\n", - "\n", - "```bash\n", - "NODE_NAME=syft NODE_PORT=8080 && \\\n", - "k3d cluster create syft -p \"$NODE_PORT:80@loadbalancer\" --registry-use k3d-registry.localhost || true \\\n", - "k3d cluster start syft\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "14", - "metadata": {}, - "source": [ - "## Install using Helm" - ] - }, - { - "cell_type": "markdown", - "id": "15", - "metadata": {}, - "source": [ - "```bash\n", - "helm install my-domain openmined/syft --version $SYFT_VERSION --namespace syft --create-namespace --set ingress.className=traefik\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "16", - "metadata": {}, - "source": [ - "# Upgrading using Helm" - ] - }, - { - "cell_type": "markdown", - "id": "17", - "metadata": {}, - "source": [ - "## Add Helm Repo" - ] - }, - { - "cell_type": "markdown", - "id": "18", - "metadata": {}, - "source": [ - "```bash\n", - "helm repo add openmined https://openmined.github.io/PySyft/helm\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "19", - "metadata": {}, - "source": [ - "## Update Repo" - ] - }, - { - "cell_type": "markdown", - "id": "20", - "metadata": {}, - "source": [ - "```bash\n", - "helm repo update openmined\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "21", - "metadata": {}, - "source": [ - "## Search for available Helm Chart versions" - ] - }, - { - "cell_type": "markdown", - "id": "22", - "metadata": {}, - "source": [ - "### Search for available versions" - ] - }, - { - "cell_type": "markdown", - "id": "23", - "metadata": {}, - "source": [ - "```bash\n", - "helm search repo openmined/syft --versions --devel\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "24", - "metadata": {}, - "source": [ - "### Set the target version" - ] - }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "```bash\n", - "export TARGET_VERSION=\"\"\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "26", - "metadata": {}, - "source": [ - "## Get the current Helm release values (User Defined)" - ] - }, - { - "cell_type": "markdown", - "id": "27", - "metadata": {}, - "source": [ - "Set the release name and namespace\n", - "\n", - "```bash\n", - "export RELEASE_NAME=\"\"\n", - "export NAMESPACE=\"\"\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "28", - "metadata": {}, - "source": [ - "```bash\n", - "helm get values $RELEASE_NAME -n $NAMESPACE -o yaml > values.yaml\n", - "```\n", - "\n", - "
\n", - "\n", - "Use this file in the argument to helm upgrade command, for example:\n", - "\n", - "\n", - "`-f /home/user/values.yaml`\n", - "\n", - "\n", - "Save the path to a variable:\n", - "\n", - "```bash\n", - "export PATH_TO_VALUES=/home/user/values.yaml\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "29", - "metadata": {}, - "source": [ - "## Upgrade the Helm Chart" - ] - }, - { - "cell_type": "markdown", - "id": "30", - "metadata": {}, - "source": [ - "### Find out the number of nodes in the cluster." - ] - }, - { - "cell_type": "markdown", - "id": "31", - "metadata": {}, - "source": [ - "```bash\n", - "kubectl describe sts --namespace $NAMESPACE | grep 'Replicas'\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "32", - "metadata": {}, - "source": [ - "### Upgrade the Helm chart." - ] - }, - { - "cell_type": "markdown", - "id": "33", - "metadata": {}, - "source": [ - "```bash\n", - "helm upgrade $RELEASE_NAME openmined/syft \\\n", - " --version $TARGET_VERSION \\\n", - " -f $PATH_TO_VALUES \\\n", - " --namespace $NAMESPACE\n", - "```" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From ca7cee0664ea5184316263c1a1fcfae37b2301ee Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 14:51:16 +0530 Subject: [PATCH 104/114] remove orchestra from imports --- packages/hagrid/hagrid/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index eabd22f9f19..c26d2694e22 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -10,8 +10,6 @@ from .version import __version__ # noqa: F401 from .wizard_ui import WizardUI -from .orchestra import Orchestra # noqa - def module_property(func: Any) -> None: """Decorator to turn module functions into properties. From a4d1729d114bf4b904181b69cc924f4d2172cdf8 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 15:09:13 +0530 Subject: [PATCH 105/114] updated ReadMe for hagrid deprecation --- README.md | 25 +++------- packages/syft/PYPI.md | 113 +++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 25230bb43f9..8c26dd5c2ef 100644 --- a/README.md +++ b/README.md @@ -108,18 +108,11 @@ For Google GKE we need the [`gce` annotation](https://cloud.google.com/kubernete helm install ... --set ingress.class="gce" ``` -## Deploy to a Container Engine or Cloud +## Note: -1. Install our handy πŸ›΅ cli tool which makes deploying a Domain or Gateway server to Docker or VM a one-liner: - `pip install -U hagrid` +🚨 Our deployment tool `Hagrid` is `Deprecated` .For the updated deployment options kindly refer to -2. Then run our interactive jupyter Install πŸ§™πŸ½β€β™‚οΈ WizardBETA: - `hagrid quickstart` - -3. In the tutorial you will learn how to install and deploy: - `PySyft` = our `numpy`-like 🐍 Python library for computing on `private data` in someone else's `Domain` - - `PyGrid` = our 🐳 `docker` / 🐧 `vm` `Domain` & `Gateway` Servers where `private data` lives +- πŸ“š Deployments ## Docs and Support @@ -128,10 +121,8 @@ helm install ... --set ingress.class="gce" # Install Notes -- HAGrid 0.3 Requires: 🐍 `python` πŸ™ `git` - Run: `pip install -U hagrid` -- Interactive Install πŸ§™πŸ½β€β™‚οΈ WizardBETA Requires πŸ›΅ `hagrid`: - Run: `hagrid quickstart` - PySyft 0.8.1 Requires: 🐍 `python 3.10 - 3.12` - Run: `pip install -U syft` -- PyGrid Requires: 🐳 `docker`, 🦦 `podman` or ☸️ `kubernetes` - Run: `hagrid launch ...` +- PyGrid Requires: 🐳 `docker`, 🦦 `podman` or ☸️ `kubernetes` # Versions @@ -154,13 +145,9 @@ Deprecated: PySyft and PyGrid use the same `version` and its best to match them up where possible. We release weekly betas which can be used in each context: -PySyft (Stable): `pip install -U syft` -PyGrid (Stable) `hagrid launch ... tag=latest` - -PySyft (Beta): `pip install -U syft --pre` -PyGrid (Beta): `hagrid launch ... tag=beta` +PySyft (Stable): `pip install -U syft` -HAGrid is a cli / deployment tool so the latest version of `hagrid` is usually the best. +PySyft (Beta): `pip install -U syft --pre` # What is Syft? diff --git a/packages/syft/PYPI.md b/packages/syft/PYPI.md index 24f9dd81843..30c72ebf768 100644 --- a/packages/syft/PYPI.md +++ b/packages/syft/PYPI.md @@ -1,7 +1,7 @@


-Syft Logo +Syft Logo Perform data science on `data` that remains in `someone else's` server @@ -41,20 +41,20 @@ domain_client = sy.login(port=8080, email="info@openmined.org", password="change ## PySyft in 10 minutes -πŸ“ API Example Notebooks - -- 00-load-data.ipynb -- 01-submit-code.ipynb -- 02-review-code-and-approve.ipynb -- 03-data-scientist-download-result.ipynb -- 04-jax-example.ipynb -- 05-custom-policy.ipynb -- 06-multiple-code-requests.ipynb -- 07-domain-register-control-flow.ipynb -- 08-code-version.ipynb -- 09-blob-storage.ipynb -- 10-container-images.ipynb -- 11-container-images-k8s.ipynb +πŸ“ API Example Notebooks + +- 00-load-data.ipynb +- 01-submit-code.ipynb +- 02-review-code-and-approve.ipynb +- 03-data-scientist-download-result.ipynb +- 04-jax-example.ipynb +- 05-custom-policy.ipynb +- 06-multiple-code-requests.ipynb +- 07-domain-register-control-flow.ipynb +- 08-code-version.ipynb +- 09-blob-storage.ipynb +- 10-container-images.ipynb +- 11-container-images-k8s.ipynb ## Deploy Kubernetes Helm Chart @@ -105,18 +105,11 @@ For Google GKE we need the [`gce` annotation](https://cloud.google.com/kubernete helm install ... --set ingress.class="gce" ``` -## Deploy to a Container Engine or Cloud +## Note: -1. Install our handy πŸ›΅ cli tool which makes deploying a Domain or Gateway server to Docker or VM a one-liner: - `pip install -U hagrid` +🚨 Our deployment tool `Hagrid` is `Deprecated` .For the updated deployment options kindly refer to -2. Then run our interactive jupyter Install πŸ§™πŸ½β€β™‚οΈ WizardBETA: - `hagrid quickstart` - -3. In the tutorial you will learn how to install and deploy: - `PySyft` = our `numpy`-like 🐍 Python library for computing on `private data` in someone else's `Domain` - - `PyGrid` = our 🐳 `docker` / 🐧 `vm` `Domain` & `Gateway` Servers where `private data` lives +- πŸ“š Deployments ## Docs and Support @@ -125,10 +118,8 @@ helm install ... --set ingress.class="gce" # Install Notes -- HAGrid 0.3 Requires: 🐍 `python` πŸ™ `git` - Run: `pip install -U hagrid` -- Interactive Install πŸ§™πŸ½β€β™‚οΈ WizardBETA Requires πŸ›΅ `hagrid`: - Run: `hagrid quickstart` - PySyft 0.8.1 Requires: 🐍 `python 3.10 - 3.12` - Run: `pip install -U syft` -- PyGrid Requires: 🐳 `docker`, 🦦 `podman` or ☸️ `kubernetes` - Run: `hagrid launch ...` +- PyGrid Requires: 🐳 `docker`, 🦦 `podman` or ☸️ `kubernetes` # Versions @@ -151,17 +142,13 @@ Deprecated: PySyft and PyGrid use the same `version` and its best to match them up where possible. We release weekly betas which can be used in each context: -PySyft (Stable): `pip install -U syft` -PyGrid (Stable) `hagrid launch ... tag=latest` - -PySyft (Beta): `pip install -U syft --pre` -PyGrid (Beta): `hagrid launch ... tag=beta` +PySyft (Stable): `pip install -U syft` -HAGrid is a cli / deployment tool so the latest version of `hagrid` is usually the best. +PySyft (Beta): `pip install -U syft --pre` # What is Syft? -Syft +Syft `Syft` is OpenMined's `open source` stack that provides `secure` and `private` Data Science in Python. Syft decouples `private data` from model training, using techniques like [Federated Learning](https://ai.googleblog.com/2017/04/federated-learning-collaborative.html), [Differential Privacy](https://en.wikipedia.org/wiki/Differential_privacy), and [Encrypted Computation](https://en.wikipedia.org/wiki/Homomorphic_encryption). This is done with a `numpy`-like interface and integration with `Deep Learning` frameworks, so that you as a `Data Scientist` can maintain your current workflow while using these new `privacy-enhancing techniques`. @@ -179,19 +166,19 @@ No more cold calls to get `access` to a dataset. No more weeks of `wait times` t
- +

Data Owner

-
+

Data Scientist

- +

Data Engineer

@@ -199,11 +186,11 @@ No more cold calls to get `access` to a dataset. No more weeks of `wait times` t -- Deploy a Domain Server -- Upload Private Data -- Create Accounts +- Deploy a Domain Server +- Upload Private Data +- Create Accounts - Manage Privacy Budget -- Join a Network +- Join a Network - Learn how PETs streamline Data Policies @@ -291,9 +278,9 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
- + - +
@@ -301,7 +288,7 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
- +

πŸŽ₯ PETs: Remote Data Science Unleashed - R gov 2021
@@ -320,9 +307,9 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat

- + - +
@@ -336,18 +323,18 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
- +
- +
- +
@@ -357,51 +344,51 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat OpenMined and Syft appreciates all contributors, if you would like to fix a bug or suggest a new feature, please see our [guidelines](https://openmined.github.io/PySyft/developer_guide/index.html).
-Contributors +Contributors # Supporters @@ -411,7 +398,7 @@ OpenMined and Syft appreciates all contributors, if you would like to fix a bug `OpenMined` is a fiscally sponsored `501(c)(3)` in the USA. We are funded by our generous supporters on Open Collective.

-Contributors +Contributors # Disclaimer From c66bffbed61a3eecf5b40a3fb58fdf3e0c1bd2a3 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 15:11:07 +0530 Subject: [PATCH 106/114] update pypi readme --- packages/syft/PYPI.md | 88 +++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/syft/PYPI.md b/packages/syft/PYPI.md index 30c72ebf768..eacd86818f2 100644 --- a/packages/syft/PYPI.md +++ b/packages/syft/PYPI.md @@ -1,7 +1,7 @@


-Syft Logo +Syft Logo Perform data science on `data` that remains in `someone else's` server @@ -41,20 +41,20 @@ domain_client = sy.login(port=8080, email="info@openmined.org", password="change ## PySyft in 10 minutes -πŸ“ API Example Notebooks - -- 00-load-data.ipynb -- 01-submit-code.ipynb -- 02-review-code-and-approve.ipynb -- 03-data-scientist-download-result.ipynb -- 04-jax-example.ipynb -- 05-custom-policy.ipynb -- 06-multiple-code-requests.ipynb -- 07-domain-register-control-flow.ipynb -- 08-code-version.ipynb -- 09-blob-storage.ipynb -- 10-container-images.ipynb -- 11-container-images-k8s.ipynb +πŸ“ API Example Notebooks + +- 00-load-data.ipynb +- 01-submit-code.ipynb +- 02-review-code-and-approve.ipynb +- 03-data-scientist-download-result.ipynb +- 04-jax-example.ipynb +- 05-custom-policy.ipynb +- 06-multiple-code-requests.ipynb +- 07-domain-register-control-flow.ipynb +- 08-code-version.ipynb +- 09-blob-storage.ipynb +- 10-container-images.ipynb +- 11-container-images-k8s.ipynb ## Deploy Kubernetes Helm Chart @@ -148,7 +148,7 @@ PySyft (Beta): `pip install -U syft --pre` # What is Syft? -Syft +Syft `Syft` is OpenMined's `open source` stack that provides `secure` and `private` Data Science in Python. Syft decouples `private data` from model training, using techniques like [Federated Learning](https://ai.googleblog.com/2017/04/federated-learning-collaborative.html), [Differential Privacy](https://en.wikipedia.org/wiki/Differential_privacy), and [Encrypted Computation](https://en.wikipedia.org/wiki/Homomorphic_encryption). This is done with a `numpy`-like interface and integration with `Deep Learning` frameworks, so that you as a `Data Scientist` can maintain your current workflow while using these new `privacy-enhancing techniques`. @@ -166,19 +166,19 @@ No more cold calls to get `access` to a dataset. No more weeks of `wait times` t @@ -186,11 +186,11 @@ No more cold calls to get `access` to a dataset. No more weeks of `wait times` t @@ -278,9 +278,9 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
- + - +
@@ -288,7 +288,7 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
- +

πŸŽ₯ PETs: Remote Data Science Unleashed - R gov 2021
@@ -307,9 +307,9 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat

- + - +
@@ -323,18 +323,18 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat
@@ -344,51 +344,51 @@ Provides services to a group of `Data Owners` and `Data Scientists`, such as dat OpenMined and Syft appreciates all contributors, if you would like to fix a bug or suggest a new feature, please see our [guidelines](https://openmined.github.io/PySyft/developer_guide/index.html).
-Contributors +Contributors # Supporters
- + - + - + - + - + - + - + - + - + - + - +
- +

Data Owner

-
+

Data Scientist

- +

Data Engineer

-- Deploy a Domain Server -- Upload Private Data -- Create Accounts +- Deploy a Domain Server +- Upload Private Data +- Create Accounts - Manage Privacy Budget -- Join a Network +- Join a Network - Learn how PETs streamline Data Policies
- +
- +
- +
@@ -398,7 +398,7 @@ OpenMined and Syft appreciates all contributors, if you would like to fix a bug `OpenMined` is a fiscally sponsored `501(c)(3)` in the USA. We are funded by our generous supporters on Open Collective.

-Contributors +Contributors # Disclaimer From 39fb19c59c6293092d07fb052f6c2f4115cce357 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 15:28:54 +0530 Subject: [PATCH 107/114] added deprecation notice to hagrid --- packages/hagrid/hagrid/__init__.py | 14 ++++++++++++++ packages/hagrid/hagrid/cli.py | 8 ++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index c26d2694e22..5a58c66adf8 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -4,12 +4,26 @@ import sys from typing import Any +# third party +from rich import print + # relative from .cli import check_status as check # noqa: F401 from .quickstart_ui import QuickstartUI from .version import __version__ # noqa: F401 from .wizard_ui import WizardUI +# Add a Deprecation warning for the old `hagrid` module +print("\n" * 1) +print("=" * 80) +print("DEPRECATION WARNING") +print("=" * 80) +print("The Hagrid CLI deployment tool is now deprecated.") +print("Kindly use the new deployment options available in the below link") +print("https://github.com/OpenMined/PySyft/tree/dev/notebooks/tutorials/deployments") +print("=" * 80) +print("\n" * 1) + def module_property(func: Any) -> None: """Decorator to turn module functions into properties. diff --git a/packages/hagrid/hagrid/cli.py b/packages/hagrid/hagrid/cli.py index 0e8efdc06a4..da51e648dfe 100644 --- a/packages/hagrid/hagrid/cli.py +++ b/packages/hagrid/hagrid/cli.py @@ -87,7 +87,6 @@ from .quickstart_ui import quickstart_download_notebook from .rand_sec import generate_sec_random_password from .stable_version import LATEST_STABLE_SYFT -from .style import RichGroup from .util import fix_windows_virtualenv_api from .util import from_url from .util import shell @@ -113,7 +112,12 @@ def get_azure_image(short_name: str) -> str: raise Exception(f"Image name doesn't exist: {short_name}. Try: default or 0.7.0") -@click.group(cls=RichGroup) +class NoHelpGroup(click.Group): + def get_help(self, ctx: Any) -> str: + return "" + + +@click.group(cls=NoHelpGroup) def cli() -> None: pass From fb6e33d97bde8442568ea0a45d744e7da24ec670 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Fri, 17 May 2024 15:52:56 +0530 Subject: [PATCH 108/114] Add hagrid deprecation notice and disable all CLI commands --- packages/hagrid/hagrid/__init__.py | 25 ++++++++++++++++---- packages/hagrid/hagrid/cli.py | 37 +++++++++--------------------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index eabd22f9f19..9ac13d13d24 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -1,16 +1,33 @@ -from .git_check import verify_git_installation # noqa - # stdlib import sys from typing import Any +# third party +import rich + # relative -from .cli import check_status as check # noqa: F401 from .quickstart_ui import QuickstartUI from .version import __version__ # noqa: F401 from .wizard_ui import WizardUI -from .orchestra import Orchestra # noqa +console = rich.get_console() +table = rich.table.Table(show_header=False) +table.add_column(justify="center") +table.add_row( + "🚨🚨🚨 Hagrid has been deprecated. 🚨🚨🚨", + style=rich.style.Style( + bold=True, + color="red", + ), +) +table.add_row( + "Please refer to https://github.com/OpenMined/PySyft/notebooks/" + "tutorials/deployments/00-deployment-types.ipynb for the deployment instructions.", + style=rich.style.Style( + color=None, + ), +) +console.print(table) def module_property(func: Any) -> None: diff --git a/packages/hagrid/hagrid/cli.py b/packages/hagrid/hagrid/cli.py index 0e8efdc06a4..07bb03ca436 100644 --- a/packages/hagrid/hagrid/cli.py +++ b/packages/hagrid/hagrid/cli.py @@ -87,7 +87,6 @@ from .quickstart_ui import quickstart_download_notebook from .rand_sec import generate_sec_random_password from .stable_version import LATEST_STABLE_SYFT -from .style import RichGroup from .util import fix_windows_virtualenv_api from .util import from_url from .util import shell @@ -113,7 +112,7 @@ def get_azure_image(short_name: str) -> str: raise Exception(f"Image name doesn't exist: {short_name}. Try: default or 0.7.0") -@click.group(cls=RichGroup) +@click.command def cli() -> None: pass @@ -3457,11 +3456,6 @@ def land(args: tuple[str], **kwargs: Any) -> None: print("Hagrid land aborted.") -cli.add_command(launch) -cli.add_command(land) -cli.add_command(clean) - - @click.command( help="Show HAGrid debug information", context_settings={"show_default": True} ) @@ -3474,9 +3468,6 @@ def debug(args: tuple[str], **kwargs: Any) -> None: print("\n=================================================================\n\n") -cli.add_command(debug) - - DEFAULT_HEALTH_CHECKS = ["host", "UI (Ξ²eta)", "api", "ssh", "jupyter"] HEALTH_CHECK_FUNCTIONS = { "host": check_host, @@ -3819,9 +3810,6 @@ def check_status( print("Video Explanation: https://youtu.be/BJhlCxerQP4 \n") -cli.add_command(check) - - # add Hagrid info to the cli @click.command(help="Show HAGrid info", context_settings={"show_default": True}) def version() -> None: @@ -3830,9 +3818,6 @@ def version() -> None: print(f"HAGRID_REPO_SHA: {commit_hash()}") -cli.add_command(version) - - def run_quickstart( url: str | None = None, syft: str = "latest", @@ -4074,9 +4059,6 @@ def quickstart_cli( ) -cli.add_command(quickstart_cli, "quickstart") - - def display_jupyter_url(url_parts: tuple[str, str, int]) -> None: url = url_parts[0] if is_gitpod(): @@ -4236,9 +4218,6 @@ def dagobah(zip_file: str) -> None: return run_quickstart(zip_file=zip_file) -cli.add_command(dagobah) - - def ssh_into_remote_machine( host_ip: str, username: str, @@ -4326,9 +4305,6 @@ def ssh(ip_address: str, cmd: str) -> None: ) -cli.add_command(ssh) - - # Add hagrid logs command to the CLI @click.command( help="Get the logs of the HAGrid node", context_settings={"show_default": True} @@ -4401,4 +4377,13 @@ def logs(domain_name: str) -> None: # nosec + " [bold green]HAPPY DEBUGGING! πŸ›πŸžπŸ¦—πŸ¦ŸπŸ¦ πŸ¦ πŸ¦ [/bold green]\n " ) -cli.add_command(logs) +# cli.add_command(launch) +# cli.add_command(land) +# cli.add_command(clean) +# cli.add_command(debug) +# cli.add_command(check) +# cli.add_command(version) +# cli.add_command(quickstart_cli, "quickstart") +# cli.add_command(dagobah) +# cli.add_command(ssh) +# cli.add_command(logs) From db688d037499aed96eb856348b2e4b3354068b48 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 16:03:27 +0530 Subject: [PATCH 109/114] Revert "added deprecation notice to hagrid" This reverts commit 39fb19c59c6293092d07fb052f6c2f4115cce357. --- packages/hagrid/hagrid/__init__.py | 14 -------------- packages/hagrid/hagrid/cli.py | 8 ++------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index 5a58c66adf8..c26d2694e22 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -4,26 +4,12 @@ import sys from typing import Any -# third party -from rich import print - # relative from .cli import check_status as check # noqa: F401 from .quickstart_ui import QuickstartUI from .version import __version__ # noqa: F401 from .wizard_ui import WizardUI -# Add a Deprecation warning for the old `hagrid` module -print("\n" * 1) -print("=" * 80) -print("DEPRECATION WARNING") -print("=" * 80) -print("The Hagrid CLI deployment tool is now deprecated.") -print("Kindly use the new deployment options available in the below link") -print("https://github.com/OpenMined/PySyft/tree/dev/notebooks/tutorials/deployments") -print("=" * 80) -print("\n" * 1) - def module_property(func: Any) -> None: """Decorator to turn module functions into properties. diff --git a/packages/hagrid/hagrid/cli.py b/packages/hagrid/hagrid/cli.py index da51e648dfe..0e8efdc06a4 100644 --- a/packages/hagrid/hagrid/cli.py +++ b/packages/hagrid/hagrid/cli.py @@ -87,6 +87,7 @@ from .quickstart_ui import quickstart_download_notebook from .rand_sec import generate_sec_random_password from .stable_version import LATEST_STABLE_SYFT +from .style import RichGroup from .util import fix_windows_virtualenv_api from .util import from_url from .util import shell @@ -112,12 +113,7 @@ def get_azure_image(short_name: str) -> str: raise Exception(f"Image name doesn't exist: {short_name}. Try: default or 0.7.0") -class NoHelpGroup(click.Group): - def get_help(self, ctx: Any) -> str: - return "" - - -@click.group(cls=NoHelpGroup) +@click.group(cls=RichGroup) def cli() -> None: pass From ba565ce6c153c3dfc614447b19293fde1e430240 Mon Sep 17 00:00:00 2001 From: Tauquir <30658453+itstauq@users.noreply.github.com> Date: Fri, 17 May 2024 16:15:19 +0530 Subject: [PATCH 110/114] Fix minor typo and stray whitespaces --- README.md | 4 ++-- packages/hagrid/hagrid/__init__.py | 1 - packages/syft/PYPI.md | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8c26dd5c2ef..48e0a38c5cb 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,9 @@ helm install ... --set ingress.class="gce" ## Note: -🚨 Our deployment tool `Hagrid` is `Deprecated` .For the updated deployment options kindly refer to +🚨 Our deployment tool `Hagrid` has been `Deprecated`. For the updated deployment options kindly refer to -- πŸ“š Deployments +- πŸ“š Deployments ## Docs and Support diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index e314d046875..9ac13d13d24 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -10,7 +10,6 @@ from .version import __version__ # noqa: F401 from .wizard_ui import WizardUI - console = rich.get_console() table = rich.table.Table(show_header=False) table.add_column(justify="center") diff --git a/packages/syft/PYPI.md b/packages/syft/PYPI.md index eacd86818f2..516d053e0e8 100644 --- a/packages/syft/PYPI.md +++ b/packages/syft/PYPI.md @@ -107,9 +107,9 @@ helm install ... --set ingress.class="gce" ## Note: -🚨 Our deployment tool `Hagrid` is `Deprecated` .For the updated deployment options kindly refer to +🚨 Our deployment tool `Hagrid` has been `Deprecated`. For the updated deployment options kindly refer to -- πŸ“š Deployments +- πŸ“š Deployments ## Docs and Support From c435bc1e6b752766e0db52e1abdd17f5876b0eb9 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 16:27:18 +0530 Subject: [PATCH 111/114] adding hyperlink to the deprecation --- packages/hagrid/hagrid/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/hagrid/hagrid/__init__.py b/packages/hagrid/hagrid/__init__.py index 9ac13d13d24..55e2021ff1f 100644 --- a/packages/hagrid/hagrid/__init__.py +++ b/packages/hagrid/hagrid/__init__.py @@ -4,6 +4,7 @@ # third party import rich +from rich.text import Text # relative from .quickstart_ui import QuickstartUI @@ -20,13 +21,12 @@ color="red", ), ) -table.add_row( - "Please refer to https://github.com/OpenMined/PySyft/notebooks/" - "tutorials/deployments/00-deployment-types.ipynb for the deployment instructions.", - style=rich.style.Style( - color=None, - ), -) +link = "https://github.com/OpenMined/PySyft/tree/dev/notebooks/tutorials/deployments" +link_text = Text(link, style="link " + link + " cyan") +normal_text = Text("Please refer to ") +normal_text.append(link_text) +normal_text.append(" for the deployment instructions.") +table.add_row(normal_text) console.print(table) From c10b0e413c625abde6ad0fd4c3fb4f87148e91cf Mon Sep 17 00:00:00 2001 From: alfred-openmined-bot <145415986+alfred-openmined-bot@users.noreply.github.com> Date: Fri, 17 May 2024 11:04:14 +0000 Subject: [PATCH 112/114] [hagrid] bump version --- packages/hagrid/.bumpversion.cfg | 2 +- packages/hagrid/hagrid/manifest_template.yml | 4 ++-- packages/hagrid/hagrid/version.py | 2 +- packages/hagrid/setup.py | 2 +- scripts/hagrid_hash | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/hagrid/.bumpversion.cfg b/packages/hagrid/.bumpversion.cfg index a30678ab82f..733b610d5bc 100644 --- a/packages/hagrid/.bumpversion.cfg +++ b/packages/hagrid/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.121 +current_version = 0.3.122 tag = False tag_name = {new_version} commit = True diff --git a/packages/hagrid/hagrid/manifest_template.yml b/packages/hagrid/hagrid/manifest_template.yml index 552fdb3efd7..eb7c5c9b3d6 100644 --- a/packages/hagrid/hagrid/manifest_template.yml +++ b/packages/hagrid/hagrid/manifest_template.yml @@ -1,9 +1,9 @@ manifestVersion: 0.1 -hagrid_version: 0.3.121 +hagrid_version: 0.3.122 syft_version: 0.8.7-beta.7 dockerTag: 0.8.7-beta.7 baseUrl: https://raw.githubusercontent.com/OpenMined/PySyft/ -hash: 4333433d5bec7bb9bcd52db59029d3bcb23c74c2 +hash: a4268fab7ad76cd2e854b259660879f15e213824 target_dir: ~/.hagrid/PySyft/ files: grid: diff --git a/packages/hagrid/hagrid/version.py b/packages/hagrid/hagrid/version.py index 22a3553ca00..6d599e5c8e7 100644 --- a/packages/hagrid/hagrid/version.py +++ b/packages/hagrid/hagrid/version.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # HAGrid Version -__version__ = "0.3.121" +__version__ = "0.3.122" if __name__ == "__main__": print(__version__) diff --git a/packages/hagrid/setup.py b/packages/hagrid/setup.py index 5dc9c72f5e4..1d273539441 100644 --- a/packages/hagrid/setup.py +++ b/packages/hagrid/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages from setuptools import setup -__version__ = "0.3.121" +__version__ = "0.3.122" DATA_FILES = {"img": ["hagrid/img/*.png"], "hagrid": ["*.yml"]} diff --git a/scripts/hagrid_hash b/scripts/hagrid_hash index 715b59990cc..6372fec9252 100644 --- a/scripts/hagrid_hash +++ b/scripts/hagrid_hash @@ -1 +1 @@ -56f89d45a711a6bf79a460fc8cd4ae20 +d18b821cb0778e437426c9a066158bec From 29331025a617ca6731e655e83e43784803d82644 Mon Sep 17 00:00:00 2001 From: rasswanth-s <43314053+rasswanth-s@users.noreply.github.com> Date: Fri, 17 May 2024 16:39:52 +0530 Subject: [PATCH 113/114] disable arm test temporarily --- .github/workflows/pr-tests-stack-arm64.yml | 206 +++++++++++---------- 1 file changed, 104 insertions(+), 102 deletions(-) diff --git a/.github/workflows/pr-tests-stack-arm64.yml b/.github/workflows/pr-tests-stack-arm64.yml index 6dc275c8f6b..7bb4e07de40 100644 --- a/.github/workflows/pr-tests-stack-arm64.yml +++ b/.github/workflows/pr-tests-stack-arm64.yml @@ -1,102 +1,104 @@ -name: PR Tests - Stack - Arm64 - -on: - workflow_call: - - workflow_dispatch: - inputs: - none: - description: "Run Version Tests Manually" - required: false - -concurrency: - group: stackarm64-${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || github.workflow_ref }} - cancel-in-progress: true - -jobs: - pr-tests-stack-arm64: - strategy: - max-parallel: 3 - matrix: - os: [ubuntu-latest] - python-version: ["3.12"] - - runs-on: ${{matrix.os}} - - steps: - # - name: set permissions on work folder for self-runners - # run: | - # sudo chown -R $USER:$USER ~/actions-runner/_work/ - - - uses: actions/checkout@v4 - - # free 10GB of space - - name: Remove unnecessary files - if: matrix.os == 'ubuntu-latest' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - docker image prune --all --force - docker builder prune --all --force - docker system prune --all --force - - - name: Check for file changes - uses: dorny/paths-filter@v3 - id: changes - with: - base: ${{ github.ref }} - token: ${{ github.token }} - filters: .github/file-filters.yml - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - run: | - pip install --upgrade pip uv==0.1.35 - uv --version - - # - name: Get pip cache dir - # id: pip-cache - # shell: bash - # run: | - # echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT - - # - name: pip cache - # uses: actions/cache@v3 - # with: - # path: ${{ steps.pip-cache.outputs.dir }} - # key: ${{ runner.os }}-uv-py${{ matrix.python-version }} - # restore-keys: | - # ${{ runner.os }}-uv-py${{ matrix.python-version }} - - - name: Install tox - run: | - pip install --upgrade tox tox-uv==1.5.1 - - - name: Install Docker Compose - if: runner.os == 'Linux' - shell: bash - run: | - mkdir -p ~/.docker/cli-plugins - DOCKER_COMPOSE_VERSION=v2.21.0 - curl -sSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose - chmod +x ~/.docker/cli-plugins/docker-compose - - - name: Setup linux/arm64 Docker - run: | - docker rm $(docker ps -aq) --force || true - docker volume prune -f || true - docker buildx create --platform linux/arm64 --name arm64builder || true - docker buildx use arm64builder || true - docker run --privileged --rm tonistiigi/binfmt --install arm64 - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - - name: Run integration tests - uses: nick-fields/retry@v3 - with: - timeout_seconds: 36000 - max_attempts: 3 - command: EMULATION="true" HAGRID_FLAGS="--tag=local --test --platform linux/arm64" tox -e stack.test.integration +# The following test is disable temporarily until we switch to +# Self hosted runners for running the arm64 tests +# name: PR Tests - Stack - Arm64 + +# on: +# workflow_call: + +# workflow_dispatch: +# inputs: +# none: +# description: "Run Version Tests Manually" +# required: false + +# concurrency: +# group: stackarm64-${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || github.workflow_ref }} +# cancel-in-progress: true + +# jobs: +# pr-tests-stack-arm64: +# strategy: +# max-parallel: 3 +# matrix: +# os: [ubuntu-latest] +# python-version: ["3.12"] + +# runs-on: ${{matrix.os}} + +# steps: +# # - name: set permissions on work folder for self-runners +# # run: | +# # sudo chown -R $USER:$USER ~/actions-runner/_work/ + +# - uses: actions/checkout@v4 + +# # free 10GB of space +# - name: Remove unnecessary files +# if: matrix.os == 'ubuntu-latest' +# run: | +# sudo rm -rf /usr/share/dotnet +# sudo rm -rf "$AGENT_TOOLSDIRECTORY" +# docker image prune --all --force +# docker builder prune --all --force +# docker system prune --all --force + +# - name: Check for file changes +# uses: dorny/paths-filter@v3 +# id: changes +# with: +# base: ${{ github.ref }} +# token: ${{ github.token }} +# filters: .github/file-filters.yml + +# - name: Set up Python ${{ matrix.python-version }} +# uses: actions/setup-python@v5 +# with: +# python-version: ${{ matrix.python-version }} + +# - name: Upgrade pip +# run: | +# pip install --upgrade pip uv==0.1.35 +# uv --version + +# # - name: Get pip cache dir +# # id: pip-cache +# # shell: bash +# # run: | +# # echo "dir=$(uv cache dir)" >> $GITHUB_OUTPUT + +# # - name: pip cache +# # uses: actions/cache@v3 +# # with: +# # path: ${{ steps.pip-cache.outputs.dir }} +# # key: ${{ runner.os }}-uv-py${{ matrix.python-version }} +# # restore-keys: | +# # ${{ runner.os }}-uv-py${{ matrix.python-version }} + +# - name: Install tox +# run: | +# pip install --upgrade tox tox-uv==1.5.1 + +# - name: Install Docker Compose +# if: runner.os == 'Linux' +# shell: bash +# run: | +# mkdir -p ~/.docker/cli-plugins +# DOCKER_COMPOSE_VERSION=v2.21.0 +# curl -sSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose +# chmod +x ~/.docker/cli-plugins/docker-compose + +# - name: Setup linux/arm64 Docker +# run: | +# docker rm $(docker ps -aq) --force || true +# docker volume prune -f || true +# docker buildx create --platform linux/arm64 --name arm64builder || true +# docker buildx use arm64builder || true +# docker run --privileged --rm tonistiigi/binfmt --install arm64 +# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +# - name: Run integration tests +# uses: nick-fields/retry@v3 +# with: +# timeout_seconds: 36000 +# max_attempts: 3 +# command: tox -e stack.test.integration From e4e0daab400769256d2f54a02ba3181395bf4755 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 09:12:36 +0000 Subject: [PATCH 114/114] Bump crazy-max/ghaction-setup-docker from 3.1.0 to 3.2.0 Bumps [crazy-max/ghaction-setup-docker](https://github.com/crazy-max/ghaction-setup-docker) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/crazy-max/ghaction-setup-docker/releases) - [Commits](https://github.com/crazy-max/ghaction-setup-docker/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: crazy-max/ghaction-setup-docker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-tests-frontend.yml | 2 +- .github/workflows/pr-tests-syft.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-tests-frontend.yml b/.github/workflows/pr-tests-frontend.yml index 7d669b61da8..47d90ecfb52 100644 --- a/.github/workflows/pr-tests-frontend.yml +++ b/.github/workflows/pr-tests-frontend.yml @@ -67,7 +67,7 @@ jobs: - name: Docker on MacOS if: steps.changes.outputs.frontend == 'true' && matrix.os == 'macos-latest' - uses: crazy-max/ghaction-setup-docker@v3.1.0 + uses: crazy-max/ghaction-setup-docker@v3.2.0 - name: Install Tox if: steps.changes.outputs.frontend == 'true' diff --git a/.github/workflows/pr-tests-syft.yml b/.github/workflows/pr-tests-syft.yml index f2bee6a78cf..046dea143e0 100644 --- a/.github/workflows/pr-tests-syft.yml +++ b/.github/workflows/pr-tests-syft.yml @@ -86,7 +86,7 @@ jobs: # - name: Docker on MacOS # if: steps.changes.outputs.syft == 'true' && matrix.os == 'macos-latest' - # uses: crazy-max/ghaction-setup-docker@v3.1.0 + # uses: crazy-max/ghaction-setup-docker@v3.2.0 # with: # set-host: true @@ -278,7 +278,7 @@ jobs: - name: Docker on MacOS if: (steps.changes.outputs.stack == 'true' || steps.changes.outputs.notebooks == 'true') && matrix.os == 'macos-latest' - uses: crazy-max/ghaction-setup-docker@v3.1.0 + uses: crazy-max/ghaction-setup-docker@v3.2.0 - name: Docker Compose on MacOS if: (steps.changes.outputs.stack == 'true' || steps.changes.outputs.notebooks == 'true') && matrix.os == 'macos-latest'
- + - + - + - + - + - + - + - + - + - + - +