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/.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-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..370469ea0bb 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 @@ -290,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 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..bf36991a385 100644 --- a/.github/workflows/pr-tests-frontend.yml +++ b/.github/workflows/pr-tests-frontend.yml @@ -61,13 +61,13 @@ 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 }}- - 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-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/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 diff --git a/.github/workflows/pr-tests-stack-public.yml b/.github/workflows/pr-tests-stack-public.yml deleted file mode 100644 index 46f71b40b3f..00000000000 --- a/.github/workflows/pr-tests-stack-public.yml +++ /dev/null @@ -1,217 +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: - # issues with macos 14 arm https://github.com/crazy-max/ghaction-setup-docker/pull/53 - os: [ubuntu-latest, macos-13, windows-latest] - python-version: ["3.12"] - pytest-modules: ["frontend network"] - fail-fast: false - - runs-on: ${{matrix.os}} - - steps: - - name: "clean .git/config" - if: matrix.os == 'windows-latest' - 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-latest' - uses: crazy-max/ghaction-chocolatey@v3 - with: - args: list - - - name: Install git - 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-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-latest' - run: | - cmake --version - shell: cmd - - - name: Install visualcpp-build-tools - if: steps.changes.outputs.stack == 'true' && matrix.os == 'windows-latest' - 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-13' - uses: crazy-max/ghaction-setup-docker@v3.2.0 - - - name: Docker Compose on MacOS - if: steps.changes.outputs.stack == 'true' && matrix.os == 'macos-13' - 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-latest' && 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 }} 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' 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 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 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 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: 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 3ceaaf9438d..00000000000 Binary files a/notebooks/quickstart/img/edit.png and /dev/null differ diff --git a/notebooks/quickstart/img/head.png b/notebooks/quickstart/img/head.png deleted file mode 100644 index 9d220749f31..00000000000 Binary files a/notebooks/quickstart/img/head.png and /dev/null differ diff --git a/notebooks/quickstart/img/run.png b/notebooks/quickstart/img/run.png deleted file mode 100644 index c3a678a9fd1..00000000000 Binary files a/notebooks/quickstart/img/run.png and /dev/null differ diff --git a/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb b/notebooks/tutorials/model-auditing/colab/01-user-log.ipynb index 226ac4f4006..036c21f9ed6 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)" 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" 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/packages/hagrid/.bumpversion.cfg b/packages/hagrid/.bumpversion.cfg deleted file mode 100644 index 733b610d5bc..00000000000 --- a/packages/hagrid/.bumpversion.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[bumpversion] -current_version = 0.3.122 -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 1a31e027921..00000000000 Binary files a/packages/hagrid/cli2.png and /dev/null differ 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 55e2021ff1f..00000000000 --- a/packages/hagrid/hagrid/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -# stdlib -import sys -from typing import Any - -# third party -import rich -from rich.text import Text - -# relative -from .quickstart_ui import QuickstartUI -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") -table.add_row( - "šŸšØšŸšØšŸšØ Hagrid has been deprecated. šŸšØšŸšØšŸšØ", - style=rich.style.Style( - bold=True, - color="red", - ), -) -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) - - -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 07bb03ca436..00000000000 --- a/packages/hagrid/hagrid/cli.py +++ /dev/null @@ -1,4389 +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 .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.command -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.") - - -@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") - - -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") - - -# 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()}") - - -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, - ) - - -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) - - -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, - ) - - -# 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(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) 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 2b4dbd75d41..00000000000 Binary files a/packages/hagrid/hagrid/img/hagrid.png and /dev/null differ diff --git a/packages/hagrid/hagrid/img/hagrid2.png b/packages/hagrid/hagrid/img/hagrid2.png deleted file mode 100644 index 8df9d58d147..00000000000 Binary files a/packages/hagrid/hagrid/img/hagrid2.png and /dev/null differ 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 eb7c5c9b3d6..00000000000 --- a/packages/hagrid/hagrid/manifest_template.yml +++ /dev/null @@ -1,31 +0,0 @@ -manifestVersion: 0.1 -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: a4268fab7ad76cd2e854b259660879f15e213824 -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'
{tutorial_name} is not a valid tutorial name.
' - ) - 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'
{tutorial_name} downloaded.' - html += f'
šŸ“– Click to Open Tutorial
' - 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 6d599e5c8e7..00000000000 --- a/packages/hagrid/hagrid/version.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 -# HAGrid Version -__version__ = "0.3.122" - -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 1d273539441..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.122" - -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", - ) 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 6372fec9252..00000000000 --- a/scripts/hagrid_hash +++ /dev/null @@ -1 +0,0 @@ -d18b821cb0778e437426c9a066158bec 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() diff --git a/tox.ini b/tox.ini index f3763f23d0c..c79fa513cb1 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 @@ -201,145 +180,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] -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] @@ -347,7 +187,6 @@ description = Build Docs for Syft changedir = {toxinidir}/docs deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} -r {toxinidir}/docs/requirements.txt allowlist_externals = make @@ -368,7 +207,6 @@ commands = description = Jupyter Notebook with Editable Syft deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} jupyter jupyterlab commands = @@ -395,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 @@ -406,7 +243,6 @@ commands = description = Syft Unit Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} allowlist_externals = bash uv @@ -489,7 +325,6 @@ commands = description = Stack Notebook Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake changedir = {toxinidir}/notebooks allowlist_externals = @@ -525,7 +360,6 @@ commands = description = Stack VM Tests deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake allowlist_externals = cd @@ -575,7 +409,6 @@ commands = description = Stack podman Tests for Rhel & Centos deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} nbmake allowlist_externals = cd @@ -608,7 +441,6 @@ commands = description = Generate Types for Frontend deps = {[testenv:syft]deps} - {[testenv:hagrid]deps} allowlist_externals = cd bash @@ -638,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 = @@ -669,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 =