diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml new file mode 100644 index 00000000..95a1c40d --- /dev/null +++ b/.github/workflows/build_docs.yml @@ -0,0 +1,72 @@ +name: Build Docs + +on: + workflow_dispatch: + inputs: + publish_doc: + type: boolean + description: 'If set to true, then the documentation will be published.' + default: false + required: false + workflow_call: + inputs: + publish_doc: + type: boolean + description: 'If set to true, then the documentation will be published.' + default: false + required: false + +jobs: + build_docs: + name: Build Documentation + runs-on: [self-hosted, Linux, Ubuntu24.04] + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + + - name: Install Python Dependencies + run: | + python3 -m venv env + source env/bin/activate + pip install sphinx + + - name: Get Current Python Version + run: | + version="$(( python3 --version 2>&1 || echo ) | grep -Po '(?<=Python )\d+\.\d+' || true)" + echo "Found version $version" + echo "PYTHON_VERSION=$version" >> $GITHUB_ENV + + - name: Download Python Artifact + uses: actions/download-artifact@v4 + with: + name: pymgclient-linux-${{ env.PYTHON_VERSION }} + path: ./dist + + - name: Install Pymgclient + run: | + source env/bin/activate + pip install dist/*.whl + + - name: Build docs + run: | + source env/bin/activate + cd docs + make html + rm build/html/.buildinfo + touch build/html/.nojekyll + + - name: Deploy docs + if: ${{ github.event.inputs.publish_doc == 'true' }} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/build/html + + - name: Cleanup + if: always() + run: | + rm -rf env || true \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bff4761..df197471 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,188 +1,69 @@ name: CI +concurrency: + group: ${{ github.head_ref || github.sha }} + cancel-in-progress: true on: - push: + pull_request: workflow_dispatch: + inputs: + test_linux: + type: boolean + default: true + description: "Run Linux Build and Test" + test_windows: + type: boolean + default: true + description: "Run Windows Build and Test" + test_macintosh: + type: boolean + default: true + description: "Run Mac OS Build" + build_source_dist: + type: boolean + default: true + description: "Build Source Distribution" + upload_artifacts: + type: boolean + default: true + description: "Upload Artifacts" + schedule: - - cron: "0 1 * * *" - + - cron: "0 0 * * 0" jobs: - build_and_test_ubuntu: - strategy: - matrix: - include: - - {platform: 'ubuntu-20.04', python_version: '3.8', mgversion: '2.0.1'} - - {platform: 'ubuntu-20.04', python_version: '3.8', mgversion: '2.5.2'} - - {platform: 'ubuntu-20.04', python_version: '3.8', mgversion: '2.10.1'} - - {platform: 'ubuntu-22.04', python_version: '3.10', mgversion: '2.5.2'} - - {platform: 'ubuntu-22.04', python_version: '3.10', mgversion: '2.10.1'} - runs-on: ${{ matrix.platform }} - steps: - - name: Cache Memgraph community installer - id: cache-memgraph-community - uses: actions/cache@v1 - with: - path: ~/memgraph - key: cache-memgraph-v${{ matrix.mgversion }}-${{ matrix.platform }}-community-installer-v3 - - name: Download Memgraph - if: steps.cache-memgraph-community.outputs.cache-hit != 'true' - run: | - mkdir ~/memgraph - MEMGRAPH_PACKAGE_NAME="memgraph_${{ matrix.mgversion }}-1_amd64.deb" - curl -L https://download.memgraph.com/memgraph/v${{ matrix.mgversion }}/${{ matrix.platform }}/${MEMGRAPH_PACKAGE_NAME} > ~/memgraph/memgraph.deb - - name: Install system dependencies - run: | - sudo apt install -y libpython${{ matrix.python_version }} python3-pip python3-setuptools - sudo pip3 install --upgrade networkx pytest pyopenssl sphinx - sudo ln -s /dev/null /etc/systemd/system/memgraph.service # Prevents Memgraph from starting. - sudo dpkg -i ~/memgraph/memgraph.deb - - uses: actions/checkout@v2 - with: - submodules: true - - name: Build source distribution - run: python3 setup.py sdist - - name: Install pymgclient with dynamic OpenSSL for Memgraph 1.3.0 - if: matrix.mgversion == '1.3.0' - run: python3 -m pip install --global-option=build_ext --global-option="--static-openssl=false" ./dist/pymgclient-* - - name: Install pymgclient - if: matrix.mgversion != '1.3.0' - run: python3 -m pip install ./dist/pymgclient-* - - name: Import mgclient to validate installation - run: python3 -c "import mgclient" - - name: Run tests - run: | - MEMGRAPH_PORT=10000 - if [[ "${{ matrix.mgversion }}" != 1* ]]; then - python3 -m pytest -v - else - python3 -m pytest -v -m "not temporal" - fi - - name: Build docs - run: | - cd docs - make html - - name: Save source distribution package - uses: actions/upload-artifact@v2 - with: - name: pymgclient - path: dist/ + weekly_build: + if: ${{ github.event_name == 'schedule' }} + name: Weekly Build + uses: "./.github/workflows/reusable_buildtest.yml" + with: + test_linux: true + test_windows: true + test_macintosh: true + build_source_dist: false + upload_artifacts: false + secrets: inherit - build_and_test_windows: - runs-on: windows-2019 - strategy: - matrix: - arch: - - { mingw: "64", msys: x86_64, python: "x64" } - python_version: - - '3.7' - - '3.10' - env: - # TODO(gitbuda): Fix "The file cannot be accessed by the system... rocksdb_durability" - MG_VERSION: 2.8.0 - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Setup python - uses: actions/setup-python@v2.2.2 - with: - python-version: ${{ matrix.python_version }} - architecture: ${{ matrix.arch.python }} - - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW${{ matrix.arch.mingw }} - update: true - release: false - install: git mingw-w64-${{ matrix.arch.msys }}-toolchain mingw-w64-${{ matrix.arch.msys }}-cmake mingw-w64-${{ matrix.arch.msys }}-openssl - - name: Add mingw${{ matrix.arch.mingw }} to PATH - run: | - # First make sure python would resolve to the windows native python, not mingw one - echo "C:\msys64\mingw${{ matrix.arch.mingw }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 - echo "${{ env.pythonLocation }}" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 - - name: Print OpenSSL version - shell: msys2 {0} - run: | - openssl version -a - - uses: Vampire/setup-wsl@v1 - with: - distribution: Ubuntu-20.04 - - name: Download, install and run Memgraph under WSL - shell: wsl-bash {0} # root shell - run: | - mkdir ~/memgraph - curl -L https://download.memgraph.com/memgraph/v${{ env.MG_VERSION }}/ubuntu-20.04/memgraph_${{ env.MG_VERSION }}-1_amd64.deb --output ~/memgraph/memgraph.deb - dpkg -i ~/memgraph/memgraph.deb - openssl req -x509 -newkey rsa:4096 -days 3650 -nodes -keyout key.pem -out cert.pem -subj "/C=GB/ST=London/L=London/O=Testing Corp./CN=PymgclientTest" - nohup /usr/lib/memgraph/memgraph --bolt-port 7687 --bolt-cert-file="cert.pem" --bolt-key-file="key.pem" --data-directory="~/memgraph/data" --storage-properties-on-edges=true --storage-snapshot-interval-sec=0 --storage-wal-enabled=false --storage-recover-on-startup=false --storage-snapshot-on-exit=false --telemetry-enabled=false --log-file='' & - sleep 1 # Wait for Memgraph a bit. - - run: python -m pip install -U pip wheel setuptools pytest pyopenssl - - name: Build pymgclient - run: python setup.py bdist_wheel - - name: Install pymgclient - run: python -m pip install --verbose -f dist --no-index pymgclient - env: - VERBOSE: 1 - - name: Run tests - run: | - python3 -m pytest -v - env: - MEMGRAPH_HOST: "localhost" - MEMGRAPH_STARTED_WITH_SSL: - - name: Save wheel package - uses: actions/upload-artifact@v2 - with: - name: pymgclient-win${{ matrix.arch.mingw }}-${{ matrix.python_version }} - path: dist/ + pr_test: + if: ${{ github.event_name == 'pull_request' }} + name: Pull Request Tests + uses: "./.github/workflows/reusable_buildtest.yml" + with: + test_linux: true + test_windows: true + test_macintosh: true + build_source_dist: true + upload_artifacts: false + secrets: inherit - build_macos: - strategy: - fail-fast: false - matrix: - platform: [macos-13, macos-12, macos-11] - python_version: - - '3.8' - - '3.10' - include: - - {platform: [macOS-12.1, ARM64, self-hosted], python_version: '3.10'} - - {platform: [macOS-12.1, ARM64, self-hosted], python_version: '3.8'} - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Install python and OpenSSL - run: | - brew install python@${{ matrix.python_version }} openssl@1.1 - brew link --force --overwrite openssl@1.1 - openssl version -a - - name: Manage OpenSSL 3 on ARM machines - if: ${{ contains(matrix.platform, 'ARM64') }} - run: | - brew install openssl@3 - brew link --force --overwrite openssl@3 - openssl version -a - - name: Make used python version default - run: | - brew unlink python@3 && brew link --force python@${{ matrix.python_version }} - python${{ matrix.python_version }} --version - - name: Install pytest and pyopenssl - run: python${{ matrix.python_version }} -m pip install pyopenssl pytest - - name: Build pymgclient - run: python${{ matrix.python_version }} setup.py bdist_wheel - - name: Install pymgclient - run: python${{ matrix.python_version }} -m pip install -f dist --no-index pymgclient - - name: Import mgclient to validate installation - run: python${{ matrix.python_version }} -c "import mgclient" - - name: Save artifact name on x86 machines - if: ${{ !contains(matrix.platform, 'ARM64') }} - run: echo "OS_TYPE=${{ matrix.platform }}" >> $GITHUB_ENV - - name: Save artifact name on ARM64 machines - if: ${{ contains(matrix.platform, 'ARM64') }} - # Convert macOS-11.6-ARM64 to macos-11.6-arm64 to be consistent with full lowercase naming - run: echo OS_TYPE=`echo "${{ matrix.platform[0] }}-${{ matrix.platform[1] }}" | tr "[:upper:]" "[:lower:]"` >> $GITHUB_ENV - - name: Save wheel package - uses: actions/upload-artifact@v2 - with: - name: pymgclient-${{ env.OS_TYPE }}-${{ matrix.python_version }} - path: dist/ + manual_test: + if: ${{ github.event_name == 'workflow_dispatch' }} + name: Manual Test + uses: "./.github/workflows/reusable_buildtest.yml" + with: + test_linux: ${{ inputs.test_linux }} + test_windows: ${{ inputs.test_windows }} + test_macintosh: ${{ inputs.test_macintosh }} + build_source_dist: ${{ inputs.build_source_dist }} + upload_artifacts: ${{ inputs.upload_artifacts }} + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2fee6bb..f7485e61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,6 @@ name: Release + + on: workflow_dispatch: inputs: @@ -7,196 +9,76 @@ on: type: string publish_doc: description: 'If set to true, then the documentation will be published.' - default: 'false' + default: false required: false - openssl_1_version: - description: 'The expected version of OpenSSL 1, e.g.: 1.1.1o.' - required: true - type: string - openssl_3_version: - description: 'The expected version of OpenSSL 3, e.g.: 3.0.3.' - required: true - type: string + type: boolean + publish_pypi: + default: false + type: boolean + description: "Attempt to publish packages to PyPI" + test: + default: true + description: "Run test release (no docs, publish to test.pypi.org)" + type: boolean + + env: PYMGCLIENT_OVERRIDE_VERSION: "${{ github.event.inputs.version }}" jobs: - build_and_test_ubuntu: - strategy: - matrix: - include: - - {platform: 'ubuntu-20.04', python_version: '3.8', mgversion: '2.10.1'} - - {platform: 'ubuntu-22.04', python_version: '3.10', mgversion: '2.10.1'} - runs-on: ${{ matrix.platform }} - steps: - - name: Install system dependencies (Ubuntu 20.04) - run: | - sudo apt install -y libpython${{ matrix.python_version }} python3-pip python3-setuptools - sudo pip3 install --upgrade networkx pytest pyopenssl sphinx - mkdir ~/memgraph - curl -L https://download.memgraph.com/memgraph/v${{matrix.mgversion}}/ubuntu-20.04/memgraph_${{matrix.mgversion}}-1_amd64.deb > ~/memgraph/memgraph.deb - sudo ln -s /dev/null /etc/systemd/system/memgraph.service # Prevents Memgraph from starting. - sudo dpkg -i ~/memgraph/memgraph.deb - - uses: actions/checkout@v2 - with: - submodules: true - - name: Build source distribution - run: python3 setup.py sdist - - name: Install pymgclient - run: python3 -m pip install ./dist/pymgclient-* - - name: Run tests - run: MEMGRAPH_PORT=10000 python3 -m pytest - - name: Build docs - run: | - cd docs - make html - rm build/html/.buildinfo - touch build/html/.nojekyll - - name: Save source distribution package - uses: actions/upload-artifact@v2 - with: - name: pymgclient-${{ github.event.inputs.version }} - path: dist/ - - name: Deploy docs - if: ${{ github.event.inputs.publish_doc == 'true' }} - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/build/html + release_tests: + name: Release Package and Test + uses: "./.github/workflows/reusable_buildtest.yml" + with: + test_linux: true + test_windows: true + test_macintosh: true + build_source_dist: true + upload_artifacts: true + version: ${{ inputs.version }} + secrets: inherit + + build_docs: + needs: [release_tests] + name: Build Docs + uses: "./.github/workflows/build_docs.yml" + with: + publish_doc: false + secrets: inherit + + publish_artifacts: + name: Collect Artifacts + runs-on: ubuntu-24.04 + needs: [release_tests, build_docs] - build_windows_and_test: - runs-on: windows-2019 - strategy: - matrix: - arch: - - { mingw: "64", msys: x86_64, python: "x64"} - python_version: - - '3.7' - - '3.8' - - '3.9' - - '3.10' - # TODO(gitbuda): Fix "The file cannot be accessed by the system... rocksdb_durability" - mgversion: - - 2.8.0 steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Setup python - uses: actions/setup-python@v2.2.2 - with: - python-version: ${{ matrix.python_version }} - architecture: ${{ matrix.arch.python }} - - uses: msys2/setup-msys2@v2 + - name: Download all artifacts + uses: actions/download-artifact@v4 with: - msystem: MINGW${{ matrix.arch.mingw }} - update: true - release: false - install: git mingw-w64-${{ matrix.arch.msys }}-toolchain mingw-w64-${{ matrix.arch.msys }}-cmake mingw-w64-${{ matrix.arch.msys }}-openssl - - name: Add mingw${{ matrix.arch.mingw }} to PATH - run: | - # First make sure python would resolve to the windows native python, not mingw one - echo "C:\msys64\mingw${{ matrix.arch.mingw }}\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 - echo "${{ env.pythonLocation }}" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 - - name: Print OpenSSL version - shell: msys2 {0} - run: | - openssl version -a - - name: Check version - shell: msys2 {0} - run: | - [ `openssl version -v | cut -d ' ' -f 2` == "${{ github.event.inputs.openssl_1_version }}" ] - - uses: Vampire/setup-wsl@v1 - with: - distribution: Ubuntu-20.04 - - name: Download, install and run Memgraph under WSL - shell: wsl-bash {0} # root shell + # omit ‘name’ to fetch *every* artifact uploaded earlier + path: ./downloaded-artifacts + + - name: Move Artifacts run: | - mkdir ~/memgraph - curl -L https://download.memgraph.com/memgraph/v${{matrix.mgversion}}/ubuntu-20.04/memgraph_${{matrix.mgversion}}-1_amd64.deb --output ~/memgraph/memgraph.deb - dpkg -i ~/memgraph/memgraph.deb - openssl req -x509 -newkey rsa:4096 -days 3650 -nodes -keyout key.pem -out cert.pem -subj "/C=GB/ST=London/L=London/O=Testing Corp./CN=PymgclientTest" - nohup /usr/lib/memgraph/memgraph --bolt-port 7687 --bolt-cert-file="cert.pem" --bolt-key-file="key.pem" --data-directory="~/memgraph/data" --storage-properties-on-edges=true --storage-snapshot-interval-sec=0 --storage-wal-enabled=false --storage-recover-on-startup=false --storage-snapshot-on-exit=false --telemetry-enabled=false --log-file='' & - sleep 1 # Wait for Memgraph a bit. - - run: python -m pip install -U pip wheel setuptools pytest pyopenssl - - name: Build pymgclient - run: python setup.py bdist_wheel - - name: Install pymgclient - run: python -m pip install --verbose -f dist --no-index pymgclient - env: - VERBOSE: 1 - - name: Run tests + mkdir -p dist + mv -v downloaded-artifacts/*/* dist/ + + - name: Show contents run: | - python -m pytest -v - env: - MEMGRAPH_HOST: "localhost" - MEMGRAPH_STARTED_WITH_SSL: - - name: Save wheel package - uses: actions/upload-artifact@v2 + ls dist/ + + - name: Publish Package to PyPI + if: ${{ inputs.publish_pypi == true && inputs.test == false }} + uses: pypa/gh-action-pypi-publish@v1.4.2 with: - name: pymgclient-${{ github.event.inputs.version }}-win${{ matrix.arch.mingw }}-${{ matrix.python_version }} - path: dist/ + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} - build_macos: - strategy: - matrix: - platform: [macos-11] - python_version: - - '3.7' - - '3.8' - - '3.9' - - '3.10' - include: - - {platform: [macOS-11.6, ARM64, self-hosted], python_version: '3.10'} - - {platform: [macOS-11.6, ARM64, self-hosted], python_version: '3.9'} - - {platform: [macOS-11.6, ARM64, self-hosted], python_version: '3.8'} - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Install python - run: | - brew update - brew install python@${{ matrix.python_version }} - - name: Manage OpenSSL 1 on x86 machines - if: ${{ !contains(matrix.platform, 'ARM64') }} - run: | - brew uninstall -f openssl@3 - brew install openssl@1.1 - brew upgrade openssl@1.1 - brew link --force --overwrite openssl@1.1 - openssl version -a - [ `openssl version -v | cut -d ' ' -f 2` == "${{ github.event.inputs.openssl_1_version }}" ] - - name: Manage OpenSSL 3 on ARM machines - if: ${{ contains(matrix.platform, 'ARM64') }} - run: | - brew install openssl@3 - brew upgrade openssl@3 - brew link --force --overwrite openssl@3 - openssl version -a - [ `openssl version -v | cut -d ' ' -f 2` == "${{ github.event.inputs.openssl_3_version }}" ] - - name: Make used python version default - run: | - brew unlink python@3 && brew link --force python@${{ matrix.python_version }} - python${{ matrix.python_version }} --version - - name: Install pytest and pyopenssl - run: python${{ matrix.python_version }} -m pip install pyopenssl pytest - - name: Build pymgclient - run: python${{ matrix.python_version }} setup.py bdist_wheel - - name: Install pymgclient - run: python${{ matrix.python_version }} -m pip install -f dist --no-index pymgclient - - name: Import mgclient to validate installation - run: python${{ matrix.python_version }} -c "import mgclient" - - name: Save artifact name on x86 machines - if: ${{ !contains(matrix.platform, 'ARM64') }} - run: echo "OS_TYPE=${{ matrix.platform }}" >> $GITHUB_ENV - - name: Save artifact name on ARM64 machines - if: ${{ contains(matrix.platform, 'ARM64') }} - # Convert macOS-11.6-ARM64 to macos-11.6-arm64 to be consistent with full lowercase naming - run: echo OS_TYPE=`echo "${{ matrix.platform[0] }}-${{ matrix.platform[1] }}" | tr "[:upper:]" "[:lower:]"` >> $GITHUB_ENV - - name: Save wheel package - uses: actions/upload-artifact@v2 + - name: Publish Package to PyPI (TEST) + if: ${{ inputs.publish_pypi == true && inputs.test }} + uses: pypa/gh-action-pypi-publish@v1.4.2 with: - name: pymgclient-${{ github.event.inputs.version }}-${{ env.OS_TYPE }}-${{ matrix.python_version }} - path: dist/ + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + verbose: true \ No newline at end of file diff --git a/.github/workflows/reusable_buildtest.yml b/.github/workflows/reusable_buildtest.yml new file mode 100644 index 00000000..d3b4cee5 --- /dev/null +++ b/.github/workflows/reusable_buildtest.yml @@ -0,0 +1,444 @@ +name: Reusable Build and Test + + +on: + workflow_call: + inputs: + test_linux: + type: boolean + default: true + description: "Run Linux Build and Test" + test_windows: + type: boolean + default: true + description: "Run Windows Build and Test" + test_macintosh: + type: boolean + default: true + description: "Run Mac OS Build" + build_source_dist: + type: boolean + default: true + description: "Build Source Distribution" + upload_artifacts: + type: boolean + default: true + description: "Upload Artifacts" + version: + required: false + type: string + +jobs: + build_and_test_linux: + if: ${{ inputs.test_linux }} + name: "Build and test on Linux 👍" + strategy: + fail-fast: false + matrix: + include: + - {platform: 'ubuntu-22.04', python_version: '3.10', mgversion: 'latest'} + - {platform: 'ubuntu-24.04', python_version: '3.10', mgversion: 'latest'} + - {platform: 'ubuntu-24.04', python_version: '3.11', mgversion: 'latest'} + - {platform: 'ubuntu-24.04', python_version: '3.12', mgversion: 'latest'} + - {platform: 'ubuntu-24.04', python_version: '3.13', mgversion: 'latest'} + - {platform: 'fedora-41', python_version: '3.13', mgversion: 'latest'} + runs-on: [self-hosted, X64] + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set Memgraph Version + run: | + if [[ "${{ matrix.mgversion }}" == "latest" ]]; then + mgversion=$(./tools/get_memgraph_version.sh) + else + mgversion="${{ matrix.mgversion }}" + fi + echo "MGVERSION=$mgversion" >> $GITHUB_ENV + + - name: Download Memgraph + run: | + if [[ "${{ matrix.platform }}" == "fedora-41" ]]; then + MEMGRAPH_PACKAGE_NAME="memgraph-${{ env.MGVERSION }}_1-1.x86_64.rpm" + LOCAL_PACKAGE_NAME=memgraph.rpm + else + MEMGRAPH_PACKAGE_NAME="memgraph_${{ env.MGVERSION }}-1_amd64.deb" + LOCAL_PACKAGE_NAME=memgraph.deb + fi + curl -L "https://download.memgraph.com/memgraph/v${{ env.MGVERSION }}/${{ matrix.platform }}/${MEMGRAPH_PACKAGE_NAME}" > "${LOCAL_PACKAGE_NAME}" + echo "LOCAL_PACKAGE_NAME=$LOCAL_PACKAGE_NAME" >> $GITHUB_ENV + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Launch Docker Container + run: | + platform="${{ matrix.platform }}" + tag=${platform//-/:} + if [[ "${{ inputs.version }}" != "" ]]; then + echo "Building version ${{ inputs.version }}" + docker run -d --rm \ + -e PYMGCLIENT_OVERRIDE_VERSION=${{ inputs.version }} \ + --name testcontainer "$tag" sleep infinity + else + docker run -d --rm --name testcontainer "$tag" sleep infinity + fi + + - name: Set Environment Variables + run: | + break_packages="" + static_ssl="" + if [[ "${{ matrix.platform }}" == "ubuntu-24.04" ]]; then + # this option is specific to ubuntu, not fedora + break_packages="--break-system-packages" + elif [[ "${{ matrix.platform }}" == fedora* ]]; then + # rpm distros do not ship with static lib for openssl + static_ssl="build_ext --static-openssl=False" + fi + echo "BREAK_PACKAGES=$break_packages" >> $GITHUB_ENV + echo "STATIC_SSL=$static_ssl" >> $GITHUB_ENV + + - name: Copy Repo Into Container + run: | + docker cp . testcontainer:/pymgclient + + - name: Install system dependencies + run: | + docker cp $LOCAL_PACKAGE_NAME testcontainer:/$LOCAL_PACKAGE_NAME + + # Prevents Memgraph from starting. + docker exec -i testcontainer \ + bash -c "mkdir -p /etc/systemd/system && ln -s /dev/null /etc/systemd/system/memgraph.service" + + # Install dependencies + docker exec -i testcontainer \ + bash -c "cd /pymgclient && ./tools/install_linux_deps.sh ${{ matrix.platform }} --python-version ${{ matrix.python_version }} --force-update" + + # Install Memgraph package + if [[ "${{ matrix.platform }}" == "fedora-41" ]]; then + docker exec -i testcontainer \ + bash -c "dnf install -y /$LOCAL_PACKAGE_NAME" + else + docker exec -i testcontainer \ + bash -c "dpkg -i /$LOCAL_PACKAGE_NAME" + fi + rm -v $LOCAL_PACKAGE_NAME + + + - name: Build Python Wheel + run: | + docker exec -i testcontainer \ + bash -c "cd /pymgclient && python${{ matrix.python_version }} setup.py ${{ env.STATIC_SSL }} bdist_wheel" + + - name: Audit Wheel + if: ${{ matrix.platform == 'ubuntu-24.04' }} + run: | + docker exec -i testcontainer \ + bash -c "cd /pymgclient && auditwheel repair dist/*.whl --plat manylinux_2_39_x86_64 -w dist/ && rm dist/*linux_x86_64.whl" + + + - name: Install pymgclient + run: | + docker exec -i testcontainer \ + bash -c "python${{ matrix.python_version }} -m pip install ./pymgclient/dist/pymgclient-* ${{ env.BREAK_PACKAGES }}" + + - name: Import mgclient to validate installation + run: | + docker exec -i testcontainer \ + bash -c "python${{ matrix.python_version }} -c 'import mgclient'" + + - name: Run tests + run: | + MEMGRAPH_PORT=10000 # what's this for? + + docker exec -i testcontainer \ + bash -c "cd /pymgclient && python${{ matrix.python_version }} -m pytest -v" + + - name: Build docs + run: | + docker exec -i testcontainer \ + bash -c "cd /pymgclient/docs && make html" + + - name: Copy Package + run: | + docker cp testcontainer:/pymgclient/dist . + + - name: Save source distribution package + if: ${{ inputs.upload_artifacts && matrix.platform == 'ubuntu-24.04' }} + uses: actions/upload-artifact@v4 + with: + name: pymgclient-linux-${{ matrix.python_version }} + path: dist/ + + - name: Cleanup + if: always() + run: | + docker stop testcontainer || echo "Container does not exist" + docker wait testcontainer || echo "Container does not exist" + docker rmi ${{ env.MGVERSION }} || echo "Image does not exist" + + build_source_dist: + if: ${{ inputs.build_source_dist }} + name: Build Source Distribution + runs-on: [self-hosted, X64, Ubuntu24.04] + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set override version if provided + if: ${{ inputs.version != '' }} + run: | + echo "Building version ${{ inputs.version }}" + echo "PYMGCLIENT_OVERRIDE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV + + - name: Create Python Virtual Environment + run: | + python3 -m venv env + source env/bin/activate + pip install setuptools wheel + + - name: Build Python Source Distribution + run: | + source env/bin/activate + python setup.py sdist + + - name: Save source distribution package + if: ${{ inputs.upload_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: pymgclient-linux-sdist + path: dist + + - name: Cleanup + if: always() + run: | + rm -fr env || true + rm -fr dist || true + + + + build_and_test_windows: + if: ${{ inputs.test_windows }} + name: Build and Test on Windows + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-2022, windows-2025] + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set override version if provided + if: ${{ inputs.version != '' }} + shell: bash + run: | + echo "Building version ${{ inputs.version }}" + echo "PYMGCLIENT_OVERRIDE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV + + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: > + git + mingw-w64-x86_64-gcc + mingw-w64-x86_64-cmake + mingw-w64-x86_64-make + mingw-w64-x86_64-openssl + + - name: Add MSYS2 mingw64/bin to PATH + shell: msys2 {0} + run: | + echo "/mingw64/bin" >> $GITHUB_PATH + + + - name: Set up Windows Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + + - name: Add Windows Python to PATH + shell: msys2 {0} + run: | + echo "$pythonLocation" >> $GITHUB_PATH + env: + pythonLocation: ${{ env.pythonLocation }} + + + - name: Install Python build tools + shell: msys2 {0} + run: | + export PATH="$(cygpath -u "$pythonLocation"):$PATH" + python -m pip install --upgrade pip setuptools wheel pyopenssl pytest + env: + pythonLocation: ${{ env.pythonLocation }} + + - name: Build pymgclient Wheel + shell: msys2 {0} + run: | + export PATH="$(cygpath -u "$pythonLocation"):$PATH" + python setup.py bdist_wheel + env: + pythonLocation: ${{ env.pythonLocation }} + + - name: Install built wheel + shell: msys2 {0} + run: | + export PATH="$(cygpath -u "$pythonLocation"):$PATH" + python -m pip install dist/*.whl + env: + pythonLocation: ${{ env.pythonLocation }} + + - name: Setup WSL Ubuntu + uses: Vampire/setup-wsl@v5 + with: + distribution: Ubuntu-24.04 + + - name: Set Memgraph Version + shell: bash -l {0} + run: | + mgversion=$(./tools/get_memgraph_version.sh) + echo "MGVERSION=$mgversion" >> $GITHUB_ENV + + - name: Install and Run Memgraph in WSL + shell: wsl-bash {0} + run: | + mkdir -p $HOME/memgraph/data + sudo apt update + sudo apt install -y curl + curl -L https://download.memgraph.com/memgraph/v${{ env.MGVERSION }}/ubuntu-24.04/memgraph_${{ env.MGVERSION }}-1_amd64.deb -o memgraph.deb + sudo mkdir -p /etc/systemd/system && sudo ln -s /dev/null /etc/systemd/system/memgraph.service # Prevents Memgraph from starting. + sudo apt install ./memgraph.deb + openssl req -x509 -newkey rsa:4096 -days 3650 -nodes -keyout key.pem -out cert.pem -subj "/C=GB/ST=London/L=London/O=Testing Corp./CN=PymgclientTest" + nohup /usr/lib/memgraph/memgraph --bolt-port 7687 --bolt-cert-file="cert.pem" --bolt-key-file="key.pem" --data-directory="~/memgraph/data" --storage-properties-on-edges=true --storage-snapshot-interval-sec=0 --storage-wal-enabled=false --storage-snapshot-on-exit=false --telemetry-enabled=false --log-file='' & + + # sleep here instead of using script because it just doesn't work in Windows + sleep 3 + # sed $'s/\r$//' ./tools/wait_for_memgraph.sh > ./tools/wait_for_memgraph.unix.sh + # bash ./tools/wait_for_memgraph.unix.sh localhost + + + - name: Run Tests + shell: msys2 {0} + run: | + export PATH="$(cygpath -u "$pythonLocation"):/mingw64/bin:$PATH" + echo $PATH + python -m pytest -v + + env: + pythonLocation: ${{ env.pythonLocation }} + MEMGRAPH_HOST: localhost + MEMGRAPH_STARTED_WITH_SSL: + + + - name: Upload Wheel Artifact + if: ${{ inputs.upload_artifacts && matrix.os == 'windows-2025' }} + uses: actions/upload-artifact@v4 + with: + name: pymgclient-win-${{ matrix.python-version }} + path: dist/ + + + # NOTE: Cannot run tests on Mac OS runners because: + # - GitHub hosted runners don't have docker + # - self-hosted runner unable to log in to docker due to requiring a password to unlock the keychain + # Also - github hosted macos runners after macos-13 are all arm64 + build_macos: + if: ${{ inputs.test_macintosh }} + name: Build and test on MacOS + strategy: + fail-fast: false + matrix: + platform: [macos-15, macos-14] + python_version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + include: + - {platform: [macos-12, ARM64, self-hosted], python_version: '3.10'} + - {platform: [macos-12, ARM64, self-hosted], python_version: '3.11'} + - {platform: [macos-12, ARM64, self-hosted], python_version: '3.12'} + - {platform: [macos-12, ARM64, self-hosted], python_version: '3.13'} + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Set override version if provided + if: ${{ inputs.version != '' }} + run: | + echo "Building version ${{ inputs.version }}" + echo "PYMGCLIENT_OVERRIDE_VERSION=${{ inputs.version }}" >> $GITHUB_ENV + + - name: Install python and OpenSSL + run: | + brew install python@${{ matrix.python_version }} openssl@1.1 + brew link --force --overwrite openssl@1.1 + openssl version -a + + - name: Manage OpenSSL 3 on ARM machines + if: ${{ contains(matrix.platform, 'ARM64') }} + run: | + brew install openssl@3 + brew link --force --overwrite openssl@3 + openssl version -a + + - name: Make used python version default + run: | + brew unlink python@3 && brew link --force python@${{ matrix.python_version }} + python${{ matrix.python_version }} --version + + - name: Create Virtual Env + run: | + python${{ matrix.python_version }} -m venv env + + - name: Install pytest and pyopenssl + run: | + export PIP_BREAK_SYSTEM_PACKAGES=1 + source env/bin/activate + python${{ matrix.python_version }} -m pip install pyopenssl pytest setuptools + + - name: Build pymgclient + run: | + source env/bin/activate + python${{ matrix.python_version }} setup.py bdist_wheel + + - name: Install pymgclient + run: | + export PIP_BREAK_SYSTEM_PACKAGES=1 + source env/bin/activate + python${{ matrix.python_version }} -m pip install dist/* + + - name: Import mgclient to validate installation + run: | + source env/bin/activate + python${{ matrix.python_version }} -c "import mgclient" + + - name: Save wheel package + if: ${{ inputs.upload_artifacts }} + uses: actions/upload-artifact@v4 + with: + name: pymgclient-${{ matrix.platform[0] || matrix.platform }}-${{ matrix.python_version }} + path: dist/ + diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 3bffc137..85215de8 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -17,18 +17,19 @@ the Python programming language. Installation ############# -pymgclient has prebuilt binary packages for +pymgclient has prebuilt binary packages for `Python + `_ 3.10 - 3.13 on -* macOS BigSur (version 11) and newer on x86_64 with `Python - `_ 3.7+ +* Linux amd64 -* macOS Monterey (version 12) and newer on arm64 with `Python - `_ 3.8+ +* macOS 12 (Montery), 14 (Sonoma) and 15 (Sequoia) on arm64 -* Windows 10 x86_64 with `Python `_ 3.7+ +* Windows x86_64 -To intall pymgclient binaries on these platforms see `Install binaries`_ section -or check `Install from source`_ for other platforms. +To install pymgclient binaries on these platforms see `Install binaries`_ section. +A source distribution is also provided for other distributions and can be installed +using ``pip`` after installing `Build prerequisites`_; alternatively - +see `Install from source`_. Install binaries ################ @@ -40,7 +41,7 @@ Install binaries pymgclient can use the latest version of OpenSSL that is installed on your machine. -On macOS run:: +On Linux and macOS run:: $ pip3 install --user pymgclient @@ -52,6 +53,10 @@ Alternatively, on Windows, if the launcher is not installed, just run:: $ pip install --user pymgclient +.. note:: + Some platforms may require using the ``--break-system-packages`` flag. + + Install from source ################### @@ -60,9 +65,10 @@ pymgclient can be installed from source on: * all platforms that have prebuilt binaries * on various Linux distributions, including: - * Ubuntu 18.04+ - * Debian 10+ - * CentOS 8+ + * Ubuntu 22.04+ + * Debian 11+ + * Centos 9+ + * Fedora 41+ ******************* Build prerequisites @@ -71,8 +77,8 @@ Build prerequisites pymgclient is a C wrapper around the `mgclient`_ Memgraph client library. To build it from you will need: -* Python 3.7 or newer -* Python 3.7 or newer header files +* Python 3.7 (3.9 for Mac OS) or newer +* Python 3.7 (3.9 for Mac OS) or newer header files * A C compiler supporting C11 standard * A C++ compiler (it is not used directly, but necessary for CMake to work) * Preqrequisites of `mgclient`_: @@ -88,9 +94,9 @@ First install the prerequisites: * On Debian/Ubuntu:: $ sudo apt install python3-dev cmake make gcc g++ libssl-dev -* On CentOS:: +* On CentOS/Fedora:: - $ sudo yum install -y python3-devel cmake3 make gcc gcc-c++ openssl-devel + $ sudo dnf install -y python3-devel cmake3 make gcc gcc-c++ openssl-devel After the prerequisites are installed pymgclient can be installed via pip:: diff --git a/mgclient b/mgclient index 6f59c8a4..9c1dd792 160000 --- a/mgclient +++ b/mgclient @@ -1 +1 @@ -Subproject commit 6f59c8a4216d20e42e0943cf506d0ddb6241c29b +Subproject commit 9c1dd792ffacb90cd92dac2860a4d927318919af diff --git a/pytest.ini b/pytest.ini index 3979e7df..34c53b22 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] markers = temporal: mark a test that tests temporal types. +norecursedirs = memgraph diff --git a/setup.py b/setup.py index 88688a17..32e74309 100644 --- a/setup.py +++ b/setup.py @@ -233,6 +233,7 @@ def build_mgclient_for(self, extension: Extension): "-DCMAKE_POSITION_INDEPENDENT_CODE=ON", f'-DCMAKE_C_FLAGS="{self.get_cflags()}"', f"-DOPENSSL_USE_STATIC_LIBS={'ON' if self.static_openssl else 'OFF'}", + "-DPKG_CONFIG_USE_STATIC_LIBS=ON" ] finalize_cmake_config_command = getattr(self, "finalize_cmake_config_command_" + sys.platform, None) @@ -269,7 +270,16 @@ def build_mgclient_for(self, extension: Extension): if finalize is not None: finalize(extension) - +if sys.platform == "win32": + extra_link_args = [ + "-l:libssl.a", + "-l:libcrypto.a", + "-lcrypt32", + "-lws2_32" + ] +else: + extra_link_args = None + setup( name="pymgclient", version=version, @@ -278,7 +288,7 @@ def build_mgclient_for(self, extension: Extension): author="Marin Tomic", author_email="marin.tomic@memgraph.com", license="Apache2", - python_requires=">=3.6", + python_requires=">=3.7", description="Memgraph database adapter for Python language", long_description=long_description, long_description_content_type="text/markdown", @@ -287,11 +297,13 @@ def build_mgclient_for(self, extension: Extension): "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Database", "Topic :: Database :: Front-Ends", @@ -302,11 +314,15 @@ def build_mgclient_for(self, extension: Extension): "Operating System :: Microsoft :: Windows", ], ext_modules=[ - Extension(EXTENSION_NAME, sources=sources, depends=headers, extra_compile_args=["-Werror=all", "-std=c99"]) + Extension(EXTENSION_NAME, sources=sources, depends=headers, extra_link_args=extra_link_args) ], project_urls={ "Source": "https://github.com/memgraph/pymgclient", "Documentation": "https://memgraph.github.io/pymgclient", }, cmdclass={"build_ext": BuildMgclientExt}, + install_requires=[ + "pyopenssl", + "networkx" + ] ) diff --git a/test/common.py b/test/common.py index f3c481c5..bb1a0f44 100644 --- a/test/common.py +++ b/test/common.py @@ -88,10 +88,11 @@ def start_memgraph(cert_file="", key_file=""): "--storage-properties-on-edges=true", "--storage-snapshot-interval-sec=0", "--storage-wal-enabled=false", - "--storage-recover-on-startup=false", "--storage-snapshot-on-exit=false", "--telemetry-enabled=false", "--log-file", + "--timezone", + "UTC" "", ] memgraph_process = subprocess.Popen(cmd) diff --git a/tools/get_memgraph_version.sh b/tools/get_memgraph_version.sh new file mode 100755 index 00000000..a42a1455 --- /dev/null +++ b/tools/get_memgraph_version.sh @@ -0,0 +1,8 @@ +#!/bin/bash +mgversion=$( + curl -s https://api.github.com/repos/memgraph/memgraph/releases/latest \ + | grep -m1 '"tag_name":' \ + | sed -E 's/.*"([^"]+)".*/\1/' \ + | sed 's/^v//' +) +echo "$mgversion" \ No newline at end of file diff --git a/tools/install_linux_deps.sh b/tools/install_linux_deps.sh new file mode 100755 index 00000000..dbc0f7cd --- /dev/null +++ b/tools/install_linux_deps.sh @@ -0,0 +1,196 @@ +#!/bin/bash + +# This script is for installing the dependencies required for building, +# installing and testing pymgclient + +#!/usr/bin/env bash +set -euo pipefail + +# defaults +python_version="" +distro="" +force_update=false + +usage() { + cat <] [--python-version X.Y] + Optional distro tag, e.g. ubuntu-24.04, fedora-42 + --python-version X.Y Optional Python MAJOR.MINOR (e.g. 3.12). + Defaults to system python3's MAJOR.MINOR. + --force-update Force python packages to be updated +EOF + exit 1 +} + + +# parse flags + optional positional distro +while [[ $# -gt 0 ]]; do + case "$1" in + --python-version) + if [[ -z "${2-}" ]]; then + echo "Error: --python-version needs an argument" >&2 + usage + fi + python_version="$2" + shift 2 + ;; + --force-update) + force_update=true + shift 1 + ;; + -h|--help) + usage + ;; + --*) # any other flag + echo "Unknown option: $1" >&2 + usage + ;; + *) # first non-flag is our distro + if [[ -z "$distro" ]]; then + distro="$1" + shift + else + echo "Unexpected argument: $1" >&2 + usage + fi + ;; + esac +done + + +# detect python_version if not given +if [[ -z "$python_version" ]]; then + python_version="$(python3 --version | grep -Eo '[0-9]+\.[0-9]+' )" +fi +python_binary="python${python_version}" + +# Detect distro from /etc/os-release +detect_distro() { + if [[ -r /etc/os-release ]]; then + . /etc/os-release + # Normalize common IDs + case "$ID" in + ubuntu|debian|linuxmint|fedora|centos|rhel|rocky) + # version might be "24.04" or "9" or "42" + # some versions include quotes + ver="${VERSION_ID//\"/}" + echo "${ID} ${ver}" + ;; + *) + echo "unknown-$(uname -s | tr '[:upper:]' '[:lower:]') $(uname -r)" ;; + esac + else + echo "unknown-$(uname -s | tr '[:upper:]' '[:lower:]') $(uname -r)" + fi +} + +# Ensure at least one arg +if [[ $# -gt 1 ]]; then + usage +fi + +# If distro not provided, detect it +if [[ -z "$distro" ]]; then + read distro version < <(detect_distro) + if [[ "$distro" == unknown* ]]; then + echo "Unknown distro detected" + exit 1 + fi +else + # split version from distro, e.g. `ubuntu-24.04` -> `ubuntu` `24.04` + version="${distro#*-}" + distro="${distro%%-*}" +fi +echo "Linux Distro: $distro $version" + +# detect if we need sudo or not +SUDO=() +if (( EUID != 0 )); then + if ! command -v sudo &>/dev/null; then + echo "Error: root privileges or sudo required." >&2 + exit 1 + fi + SUDO=(sudo) +fi + +DEB_DEPS=( + python${python_version} + python${python_version}-dev + python3-pip + python3-setuptools + python3-wheel + libpython${python_version} + cmake + g++ + libssl-dev + netcat-traditional + patchelf +) + +RPM_DEPS=( + python${python_version} + python${python_version}-devel + python3-pip + python3-setuptools + python3-wheel + cmake + g++ + openssl-devel + nmap-ncat +) + +install_deb() { + echo "Installing DEB dependencies..." + installed_python_version="$(( python3 --version 2>&1 || echo ) | grep -Po '(?<=Python )\d+\.\d+' || true)" + if [[ "$python_version" != "$installed_python_version" ]]; then + echo "Installed Python version ${installed_python_version} does not match requested version ${python_version}" + if [[ "$distro" == "debian" ]]; then + exit 1 + else + echo "Adding deadsnakes PPA" + "${SUDO[@]}" apt-get update + "${SUDO[@]}" apt-get install -y software-properties-common + "${SUDO[@]}" add-apt-repository -y ppa:deadsnakes/ppa + fi + fi + if [[ ("$distro" == "ubuntu" && ${version#*.} -ge 24) \ + || ("$distro" == "linuxmint" && ${version#*.} -ge 22) ]]; then + DEB_DEPS+=( libcurl4t64 ) + else + DEB_DEPS+=( libcurl4 ) + fi + "${SUDO[@]}" apt-get update + "${SUDO[@]}" apt-get install -y ${DEB_DEPS[*]} +} + +install_rpm() { + echo "Installing RPM dependencies..." + "${SUDO[@]}" dnf install -y ${RPM_DEPS[*]} +} + +case "$distro" in + debian|ubuntu|linuxmint) + install_deb + ;; + centos|fedora|rocky|rhel) + install_rpm + ;; + *) + echo "Unsupported Distro: $distro" >&2 + exit 1 + ;; +esac + +# install python dependencies +export PIP_BREAK_SYSTEM_PACKAGES=1 +pkgs=(networkx pytest pyopenssl sphinx setuptools wheel auditwheel) +if [[ $force_update == true ]]; then + "$python_binary" -m pip install --upgrade --ignore-installed ${pkgs[@]} +else + for pkg in "${pkgs[@]}"; do + echo "Installing/upgrading $pkg..." + if ! "$python_binary" -m pip install --upgrade "$pkg"; then + echo "Warning: pip failed on $pkg, continuing…" >&2 + fi + done +fi \ No newline at end of file diff --git a/tools/wait_for_memgraph.sh b/tools/wait_for_memgraph.sh new file mode 100755 index 00000000..b7e35ebc --- /dev/null +++ b/tools/wait_for_memgraph.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -euo pipefail + +TIMEOUT=10 +MEMGRAPH_CONSOLE_BINARY=${MEMGRAPH_CONSOLE_BINARY:-} + +usage() { + cat < [port] + --timeout SECONDS Max seconds to wait (default: $TIMEOUT) + Memgraph host to check + [port] Memgraph port (default: 7687) +EOF + exit 1 +} + +# --- parse flags --- +while [[ $# -gt 0 ]]; do + case $1 in + --timeout) + [[ -n "${2-}" ]] || { echo "Error: --timeout needs an argument" >&2; usage; } + TIMEOUT=$2 + shift 2 + ;; + -h|--help) + usage + ;; + *) + break + ;; + esac +done + +# --- positional args --- +(( $# >= 1 && $# <= 2 )) || usage +HOST=$1 +PORT=${2-7687} + +# --- locate mgconsole --- +if [[ -z "$MEMGRAPH_CONSOLE_BINARY" ]]; then + if command -v mgconsole &>/dev/null; then + MEMGRAPH_CONSOLE_BINARY=$(command -v mgconsole) + HAVE_MGCONSOLE=1 + else + HAVE_MGCONSOLE=0 + fi +else + if [[ ! -x "$MEMGRAPH_CONSOLE_BINARY" ]]; then + echo "Error: \$MEMGRAPH_CONSOLE_BINARY set to '$MEMGRAPH_CONSOLE_BINARY', but not executable." >&2 + exit 1 + fi + HAVE_MGCONSOLE=1 +fi + +# --- wait for a port on memgraph host with timeout --- +wait_port() { + local host=$1 port=$2 timeout=$3 start now + start=$(date +%s) + while true; do + if timeout 1 bash -c "(exec 3<>'/dev/tcp/${host}/${port}')" 2>/dev/null; then + return 0 + fi + now=$(date +%s) + (( now - start >= timeout )) && { + echo "Timeout ($timeout s) waiting for $host:$port" >&2 + return 1 + } + done +} + +# --- wait for memgraph console to respond with timeout --- +wait_for_memgraph() { + local host=$1 port=$2 timeout=$3 start now + start=$(date +%s) + while true; do + if timeout 1 bash -c "echo 'RETURN 1;' | \"$MEMGRAPH_CONSOLE_BINARY\" --host \"$host\" --port \"$port\" >/dev/null 2>&1"; then + return 0 + fi + now=$(date +%s) + (( now - start >= timeout )) && { + echo "Timeout ($timeout s) waiting for memgraph at $host:$port" >&2 + return 1 + } + sleep 0.1 + done +} + +# --- run checks --- +echo "Waiting for TCP port $HOST:$PORT (timeout ${TIMEOUT}s)..." +if wait_port "$HOST" "$PORT" "$TIMEOUT"; then + timed_out=0 +else + timed_out=1 +fi + +if (( HAVE_MGCONSOLE )); then + echo "Waiting for memgraph console on $HOST:$PORT (timeout ${TIMEOUT}s)..." + wait_for_memgraph "$HOST" "$PORT" "$TIMEOUT" +else + if [[ $timed_out == 1 ]]; then + echo "mgconsole not found" + exit 1 + fi + echo "Note: mgconsole not found; skipping memgraph-console check." +fi + +echo "Memgraph Started."