diff --git a/.dockerignore b/.dockerignore index b27c26a5..e16c604d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ venv/* */.direnv/* */.ruff_cache/* +*/.pytest_cache/* */.vscode/* */.mypy_cache/* */.pgadmin/* @@ -14,5 +15,5 @@ venv/* */.github/* */env/* Dockerfile -docker-compose.yml +docker compose.yml */.devcontainer/* diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 4fb12504..feee63fd 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -12,21 +12,224 @@ env: DOCKER_BUILDKIT: 1 jobs: - test: - name: test + changes: runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + pgdocker: ${{ steps.check.outputs.pgtag }} + buildpgdocker: ${{ steps.check.outputs.buildpg }} + pyrustdocker: ${{ steps.check.outputs.pytag }} + buildpyrustdocker: ${{ steps.check.outputs.buildpy }} + + steps: + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + pgstac: + - 'docker/pgstac/**' + pypgstac: + - 'docker/pypgstac/**' + - id: check + run: | + buildpg=false; + ref=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}; + [[ "${{ steps.filter.outputs.pgstac }}" == "true" ]] && buildpg=true || ref=main; + echo "pgtag=${{ env.REGISTRY }}/stac-utils/pgstac-postgres:$ref" >>$GITHUB_OUTPUT; + echo "buildpg=$buildpg" >>$GITHUB_OUTPUT; + buildy=false; + ref=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}; + [[ "${{ steps.filter.outputs.pypgstac }}" == "true" ]] && buildpy=true || ref=main; + echo "pytag=${{ env.REGISTRY }}/stac-utils/pgstac-pyrust:$ref" >>$GITHUB_OUTPUT; + echo "buildpy=$buildpg" >>$GITHUB_OUTPUT; + + buildpg: + name: Build and push base postgres + if: ${{ needs.changes.outputs.buildpgdocker == 'true' }} + runs-on: ubuntu-latest + needs: [changes] steps: - uses: actions/checkout@v3 - - uses: docker/setup-buildx-action@v1 - - name: builder - id: builder - uses: docker/build-push-action@v2 + - uses: docker/setup-buildx-action@v2 + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and Push Base Postgres + uses: docker/build-push-action@v4 with: context: . - load: true - push: false + target: pgstacbase-plrust + file: docker/pgstac/Dockerfile + tags: ${{ needs.changes.outputs.pgdocker }} + push: true cache-from: type=gha cache-to: type=gha, mode=max + buildpyrust: + name: Build and push base pyrust + if: ${{ needs.changes.outputs.buildpyrustdocker == 'true' }} + runs-on: ubuntu-latest + needs: [changes] + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and Push Base pyrust + uses: docker/build-push-action@v4 + with: + context: . + target: pyrustbase + file: docker/pypgstac/Dockerfile + tags: ${{ needs.changes.outputs.pyrustdocker }} + push: true + cache-from: type=gha + cache-to: type=gha, mode=max + + test: + name: test + needs: [changes, buildpg, buildpyrust] + runs-on: ubuntu-latest + container: + image: ${{ needs.changes.outputs.pyrustdocker }} + env: + PGPASSWORD: postgres + PGHOST: postgres + PGDATABASE: postgres + PGUSER: postgres + + services: + postgres: + env: + POSTGRES_PASSWORD: postgres + image: ${{ needs.changes.outputs.pgdocker }} + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + - name: Install pypgstac + working-directory: /__w/pgstac/pgstac/src/pypgstac + run: | + pip install .[dev,test,psycopg]; + - name: Run tests - run: docker run --rm ${{ steps.builder.outputs.imageid }} test + working-directory: /__w/pgstac/pgstac/docker/pypgstac/bin + run: | + ./test + + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /home/runner/work/pgstac/pgstac/dist + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /home/runner/work/pgstac/pgstac/dist + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /tmp/dist + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /tmp/dist/* + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + command: sdist + args: --out /home/runner/work/pgstac/pgstac/dist + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + # release: + # name: Release + # runs-on: ubuntu-latest + # if: "startsWith(github.ref, 'refs/tags/')" + # needs: [linux, windows, macos, sdist] + # steps: + # - uses: actions/download-artifact@v3 + # with: + # name: wheels + # - name: Publish to PyPI + # uses: PyO3/maturin-action@v1 + # env: + # MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + # with: + # command: upload + # args: --skip-existing * diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfaf581b..11b2a9a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,46 +10,56 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - release: - name: release + tag: + name: tag runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - - name: Install release dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine build - - - name: Build pypgstac release - run: | - pushd src/pypgstac - rm -rf dist - python -m build --sdist --wheel - popd - - - name: Publish pypgstac release - env: - TWINE_USERNAME: ${{ secrets.PYPI_STACUTILS_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_STACUTILS_PASSWORD }} - run: | - pushd src/pypgstac - twine upload dist/* - popd - - name: Tag Release uses: "marvinpinto/action-automatic-releases@v1.2.1" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false + + buildpg: + name: Build and push base postgres + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-postgres + - name: Build and Push Base Postgres + uses: docker/build-push-action@v4 + with: + context: . + target: pgstacbase-plrust + file: docker/pgstac/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: true + cache-from: type=gha + cache-to: type=gha, mode=max + + buildpgstac: + name: Build and push base postgres + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 + - name: Log in to the Container registry + uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -60,11 +70,178 @@ jobs: uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Build and Push Base Postgres + uses: docker/build-push-action@v4 + with: + context: . + target: pgstac-plrust + file: docker/pgstac/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: true + cache-from: type=gha + cache-to: type=gha, mode=max + + buildpyrust: + name: Build and push base pyrust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-pyrust + - name: Build and Push Base Postgres + uses: docker/build-push-action@v4 with: context: . + target: pyrustbase + file: docker/pgstac/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} push: true + cache-from: type=gha + cache-to: type=gha, mode=max + + buildpypgstac: + name: Build and push base pyrust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-pypgstac + - name: Build and Push Base Postgres + uses: docker/build-push-action@v4 + with: + context: . + target: pypgstac + file: docker/pgstac/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + push: true + cache-from: type=gha + cache-to: type=gha, mode=max + + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /home/runner/work/pgstac/pgstac/dist + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /home/runner/work/pgstac/pgstac/dist + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + target: ${{ matrix.target }} + args: --release --out /tmp/dist + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /tmp/dist/* + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + working-directory: src/pypgstac + command: sdist + args: --out /home/runner/work/pgstac/pgstac/dist + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: /home/runner/work/pgstac/pgstac/dist/* + + releasetopypi: + name: Release + runs-on: ubuntu-latest + needs: [linux, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_USERNAME: ${{ secrets.PYPI_STACUTILS_USERNAME }} + MATURIN_PASSWORD: ${{ secrets.PYPI_STACUTILS_PASSWORD }} + with: + command: upload + args: --skip-existing * diff --git a/.gitignore b/.gitignore index 157f14c5..20dc44b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,12 @@ +.so .envrc -pypgstac/dist +src/pypgstac/dist *.pyc *.egg-info *.eggs venv .direnv +src/pypgstac/target +src/pypgstac/python/pypgstac/*.so +.vscode +.ipynb_checkpoints diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee6f46dd..edb24b55 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,15 +14,34 @@ repos: - id: check-json - id: mixed-line-ending - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-symlinks + - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.231' + rev: 'v0.0.284' hooks: - id: ruff files: pypgstac\/.*\.py$ + args: [--fix, --exit-non-zero-on-fix] - repo: local hooks: + - id: dockerbuild + name: dockerbuild + entry: scripts/update + language: script + pass_filenames: false + verbose: true + fail_fast: true + files: Dockerfile$|\.rs$ + - id: stageversion + name: stageversion + entry: scripts/stageversion + language: script + pass_filenames: false + verbose: true + fail_fast: true - id: sql name: sql entry: scripts/test diff --git a/CHANGELOG.md b/CHANGELOG.md index 653ac119..1d477b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [unreleased] + +### Changed + +- update `pydantic` requirement to `~=2.0` +- update docker and ci workflows to build binary wheels for rust additions to pypgstac +- split docker into database service and python/rust container +- Modify scripts to auto-generate unreleased migration +- Add pre commit tasks to generate migration and to rebuild and compile pypgstac with maturin for rust +- Add private jsonb column to items and collections table to hold private metadata that should not be returned as part of a stac item +- Add generated columns to collections with the bounding box as a geometry and the datetime and end_datetime from the extents (this is to help with forthcoming work on collections search) +- Add PLRust to the Docker postgres image for forthcoming work to add optional PLRust functions for expensive json manipulation (including hydration) + ## [v0.7.10] + ### Fixed - Return an empty jsonb array from all_collections() when the collections table is empty, instead of NULL. Fixes #186. - Add delete trigger to collections to clean up partition_stats records and remove any partitions. Fixes #185 @@ -364,29 +378,43 @@ _TODO_ - Fixed issue with pypgstac loads which caused some writes to fail ([#18](https://github.com/stac-utils/pgstac/pull/18)) -[unreleased]: https://github.com/stac-utils/pgstac/compare/v0.6.10...HEAD -[v0.6.10]: https://github.com//stac-utils/pgstac/compare/v0.6.9...v0.6.10 -[v0.6.9]: https://github.com//stac-utils/pgstac/compare/v0.6.8...v0.6.9 -[v0.6.8]: https://github.com//stac-utils/pgstac/compare/v0.6.7...v0.6.8 -[v0.6.7]: https://github.com//stac-utils/pgstac/compare/v0.6.6...v0.6.7 -[v0.6.6]: https://github.com//stac-utils/pgstac/compare/v0.6.5...v0.6.6 -[v0.6.5]: https://github.com//stac-utils/pgstac/compare/v0.6.4...v0.6.5 -[v0.6.4]: https://github.com//stac-utils/pgstac/compare/v0.6.3...v0.6.4 -[v0.6.3]: https://github.com//stac-utils/pgstac/compare/v0.6.2...v0.6.3 -[v0.6.2]: https://github.com//stac-utils/pgstac/compare/v0.6.1...v0.6.2 -[v0.6.1]: https://github.com//stac-utils/pgstac/compare/v0.6.0...v0.6.1 -[v0.6.0]: https://github.com//stac-utils/pgstac/compare/v0.5.1...v0.6.0 -[v0.5.1]: https://github.com//stac-utils/pgstac/compare/v0.5.0...v0.5.1 -[v0.5.0]: https://github.com//stac-utils/pgstac/compare/v0.4.5...v0.5.0 -[v0.4.5]: https://github.com//stac-utils/pgstac/compare/v0.4.4...v0.4.5 -[v0.4.4]: https://github.com//stac-utils/pgstac/compare/v0.4.3...v0.4.4 -[v0.4.3]: https://github.com//stac-utils/pgstac/compare/v0.4.2...v0.4.3 -[v0.4.2]: https://github.com//stac-utils/pgstac/compare/v0.4.1...v0.4.2 -[v0.4.1]: https://github.com//stac-utils/pgstac/compare/v0.4.0...v0.4.1 -[v0.4.0]: https://github.com//stac-utils/pgstac/compare/v0.3.4...v0.4.0 -[v0.3.4]: https://github.com//stac-utils/pgstac/compare/v0.3.3...v0.3.4 -[v0.3.3]: https://github.com//stac-utils/pgstac/compare/v0.3.2...v0.3.3 -[v0.3.2]: https://github.com//stac-utils/pgstac/compare/v0.3.1...v0.3.2 -[v0.3.1]: https://github.com//stac-utils/pgstac/compare/v0.3.0...v0.3.1 -[v0.3.0]: https://github.com//stac-utils/pgstac/compare/v0.2.8...v0.3.0 -[v0.2.8]: https://github.com//stac-utils/pgstac/compare/ff02c9cee7bbb0a2de21530b0aeb34e823f2e95c...v0.2.8 +[unreleased]: https://github.com/stac-utils/pgstac/compare/v0.7.10...HEAD +[v0.7.10]: https://github.com/stac-utils/pgstac/compare/v0.7.9...v0.7.10 +[v0.7.9]: https://github.com/stac-utils/pgstac/compare/v0.7.8...v0.7.9 +[v0.7.8]: https://github.com/stac-utils/pgstac/compare/v0.7.7...v0.7.8 +[v0.7.7]: https://github.com/stac-utils/pgstac/compare/v0.7.6...v0.7.7 +[v0.7.6]: https://github.com/stac-utils/pgstac/compare/v0.7.5...v0.7.6 +[v0.7.5]: https://github.com/stac-utils/pgstac/compare/v0.7.4...v0.7.5 +[v0.7.4]: https://github.com/stac-utils/pgstac/compare/v0.7.3...v0.7.4 +[v0.7.3]: https://github.com/stac-utils/pgstac/compare/v0.7.2...v0.7.3 +[v0.7.2]: https://github.com/stac-utils/pgstac/compare/v0.7.1...v0.7.2 +[v0.7.1]: https://github.com/stac-utils/pgstac/compare/v0.7.0...v0.7.1 +[v0.7.0]: https://github.com/stac-utils/pgstac/compare/v0.6.13...v0.7.0 +[v0.6.13]: https://github.com/stac-utils/pgstac/compare/v0.6.12...v0.6.13 +[v0.6.12]: https://github.com/stac-utils/pgstac/compare/v0.6.11...v0.6.12 +[v0.6.11]: https://github.com/stac-utils/pgstac/compare/v0.6.10...v0.6.11 +[v0.6.10]: https://github.com/stac-utils/pgstac/compare/v0.6.9...v0.6.10 +[v0.6.9]: https://github.com/stac-utils/pgstac/compare/v0.6.8...v0.6.9 +[v0.6.8]: https://github.com/stac-utils/pgstac/compare/v0.6.7...v0.6.8 +[v0.6.7]: https://github.com/stac-utils/pgstac/compare/v0.6.6...v0.6.7 +[v0.6.6]: https://github.com/stac-utils/pgstac/compare/v0.6.5...v0.6.6 +[v0.6.5]: https://github.com/stac-utils/pgstac/compare/v0.6.4...v0.6.5 +[v0.6.4]: https://github.com/stac-utils/pgstac/compare/v0.6.3...v0.6.4 +[v0.6.3]: https://github.com/stac-utils/pgstac/compare/v0.6.2...v0.6.3 +[v0.6.2]: https://github.com/stac-utils/pgstac/compare/v0.6.1...v0.6.2 +[v0.6.1]: https://github.com/stac-utils/pgstac/compare/v0.6.0...v0.6.1 +[v0.6.0]: https://github.com/stac-utils/pgstac/compare/v0.5.1...v0.6.0 +[v0.5.1]: https://github.com/stac-utils/pgstac/compare/v0.5.0...v0.5.1 +[v0.5.0]: https://github.com/stac-utils/pgstac/compare/v0.4.5...v0.5.0 +[v0.4.5]: https://github.com/stac-utils/pgstac/compare/v0.4.4...v0.4.5 +[v0.4.4]: https://github.com/stac-utils/pgstac/compare/v0.4.3...v0.4.4 +[v0.4.3]: https://github.com/stac-utils/pgstac/compare/v0.4.2...v0.4.3 +[v0.4.2]: https://github.com/stac-utils/pgstac/compare/v0.4.1...v0.4.2 +[v0.4.1]: https://github.com/stac-utils/pgstac/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/stac-utils/pgstac/compare/v0.3.4...v0.4.0 +[v0.3.4]: https://github.com/stac-utils/pgstac/compare/v0.3.3...v0.3.4 +[v0.3.3]: https://github.com/stac-utils/pgstac/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/stac-utils/pgstac/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/stac-utils/pgstac/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/stac-utils/pgstac/compare/v0.2.8...v0.3.0 +[v0.2.8]: https://github.com/stac-utils/pgstac/compare/ff02c9cee7bbb0a2de21530b0aeb34e823f2e95c...v0.2.8 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8e5b5599..00000000 --- a/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM postgres:15-bullseye as pg -ENV PGSTACDOCKER=1 -ENV POSTGIS_MAJOR 3 -ENV POSTGIS_VERSION 3.3.3+dfsg-1~exp1.pgdg110+1 -ENV PYTHONPATH=/opt/src/pypgstac:/opt/python:${PYTHONPATH} -ENV PATH=/opt/bin:${PATH} -ENV PYTHONWRITEBYTECODE=1 -ENV PYTHONBUFFERED=1 - -RUN apt-get update \ - && apt-get upgrade -y \ - && apt-cache showpkg postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR - -RUN set -ex \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - python3 python-is-python3 python3-pip \ - postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR=$POSTGIS_VERSION \ - postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR-scripts \ - postgresql-$PG_MAJOR-pgtap \ - postgresql-$PG_MAJOR-partman \ - postgresql-$PG_MAJOR-plpgsql-check \ - && apt-get remove -y apt-transport-https \ - && apt-get clean && apt-get -y autoremove \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /opt/src/pypgstac/pypgstac \ - && touch /opt/src/pypgstac/pypgstac/__init__.py \ - && touch /opt/src/pypgstac/README.md \ - && echo '__version__ = "0.0.0"' > /opt/src/pypgstac/pypgstac/version.py - -COPY ./src/pypgstac/pyproject.toml /opt/src/pypgstac/pyproject.toml - -RUN \ - pip3 install --upgrade pip \ - && pip3 install /opt/src/pypgstac[dev,test,psycopg] - -COPY ./src /opt/src -COPY ./scripts/bin /opt/bin - -RUN \ - echo "initpgstac" > /docker-entrypoint-initdb.d/999_initpgstac.sh \ - && chmod +x /docker-entrypoint-initdb.d/999_initpgstac.sh \ - && chmod +x /opt/bin/* - -WORKDIR /opt/src diff --git a/docker-compose.yml b/docker-compose.yml index 8e27e0fa..75eb5bf9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,8 @@ services: image: pgstac build: context: . - dockerfile: Dockerfile + dockerfile: docker/pgstac/Dockerfile + target: pgstac platform: linux/amd64 environment: - POSTGRES_USER=username @@ -18,7 +19,23 @@ services: - "5439:5432" volumes: - pgstac-pgdata:/var/lib/postgresql/data - - ./src:/opt/src - - ./scripts/bin:/opt/bin + command: postgres + pypgstac: + container_name: pypgstac + image: pypgstac + build: + context: . + dockerfile: docker/pypgstac/Dockerfile + target: pypgstac + platform: linux/amd64 + environment: + - PGHOST=pgstac + - PGUSER=username + - PGPASSWORD=password + - PGDATABASE=postgis + volumes: + - .:/opt + depends_on: + - pgstac volumes: pgstac-pgdata: diff --git a/docker/pgstac/Dockerfile b/docker/pgstac/Dockerfile new file mode 100644 index 00000000..6199563a --- /dev/null +++ b/docker/pgstac/Dockerfile @@ -0,0 +1,71 @@ +ARG PG_MAJOR=15 +ARG POSTGIS_MAJOR=3 + +FROM postgres:${PG_MAJOR}-bullseye as pgstacbase +ARG POSTGIS_MAJOR +RUN \ + apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR \ + postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR-scripts \ + postgresql-$PG_MAJOR-pgtap \ + postgresql-$PG_MAJOR-plpgsql-check \ + postgresql-$PG_MAJOR-partman \ + && apt-get remove -y apt-transport-https \ + && apt-get clean && apt-get -y autoremove \ + && rm -rf /var/lib/apt/lists/* +COPY docker/pgstac/dbinit/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh + +FROM pgstacbase as pgstacbase-plrust +ENV PLRUSTVERSION=1.2.3 +ENV RUSTVERSION=1.70.0 +ENV PLRUSTDOWNLOADURL=https://github.com/tcdi/plrust/releases/download/ +ENV PLRUSTFILE=plrust-trusted-${PLRUSTVERSION}_${RUSTVERSION}-debian-pg${PG_MAJOR}-amd64.deb +ENV PLRUSTURL=${PLRUSTDOWNLOADURL}v${PLRUSTVERSION}/${PLRUSTFILE} +ADD $PLRUSTURL . +ENV PATH=/home/postgres/.cargo/bin:$PATH +RUN \ + apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + postgresql-server-dev-$PG_MAJOR \ + build-essential \ + ca-certificates \ + clang \ + clang-11 \ + gcc \ + git \ + gnupg \ + libssl-dev \ + llvm-11 \ + lsb-release \ + make \ + pkg-config \ + wget \ + && apt-get remove -y apt-transport-https \ + && apt-get clean && apt-get -y autoremove \ + && rm -rf /var/lib/apt/lists/* +USER postgres +RUN \ + wget -qO- https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain=${RUSTVERSION} \ + && $HOME/.cargo/bin/rustup toolchain install ${RUSTVERSION} \ + && $HOME/.cargo/bin/rustup default ${RUSTVERSION} \ + && $HOME/.cargo/bin/rustup component add rustc-dev +WORKDIR /docker-entrypoint-preinitdb.d +COPY docker/pgstac/dbinit/pgstac-rust-preinit.sh preloadplrust.sh +WORKDIR /docker-entrypoint-initdb.d +COPY docker/pgstac/dbinit/pgstac-rust.sh 991_plrust.sh + +USER root +RUN apt-get install -y /${PLRUSTFILE} + +FROM pgstacbase as pgstac +WORKDIR /docker-entrypoint-initdb.d +COPY docker/pgstac/dbinit/pgstac.sh 990_pgstac.sh +COPY src/pgstac/pgstac.sql 999_pgstac.sql + +FROM pgstacbase-plrust as pgstac-plrust +WORKDIR /docker-entrypoint-initdb.d +COPY docker/pgstac/dbinit/pgstac.sh 990_pgstac.sh +COPY src/pgstac/pgstac.sql 999_pgstac.sql diff --git a/docker/pgstac/dbinit/docker-entrypoint.sh b/docker/pgstac/dbinit/docker-entrypoint.sh new file mode 100755 index 00000000..b6c55481 --- /dev/null +++ b/docker/pgstac/dbinit/docker-entrypoint.sh @@ -0,0 +1,354 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + printf >&2 'error: both %s and %s are set (but are exclusive)\n' "$var" "$fileVar" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +# check to see if this file is being run or sourced from another script +_is_sourced() { + # https://unix.stackexchange.com/a/215279 + [ "${#FUNCNAME[@]}" -ge 2 ] \ + && [ "${FUNCNAME[0]}" = '_is_sourced' ] \ + && [ "${FUNCNAME[1]}" = 'source' ] +} + +# used to create initial postgres directories and if run as root, ensure ownership to the "postgres" user +docker_create_db_directories() { + local user; user="$(id -u)" + + mkdir -p "$PGDATA" + # ignore failure since there are cases where we can't chmod (and PostgreSQL might fail later anyhow - it's picky about permissions of this directory) + chmod 00700 "$PGDATA" || : + + # ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289 + mkdir -p /var/run/postgresql || : + chmod 03775 /var/run/postgresql || : + + # Create the transaction log directory before initdb is run so the directory is owned by the correct user + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then + mkdir -p "$POSTGRES_INITDB_WALDIR" + if [ "$user" = '0' ]; then + find "$POSTGRES_INITDB_WALDIR" \! -user postgres -exec chown postgres '{}' + + fi + chmod 700 "$POSTGRES_INITDB_WALDIR" + fi + + # allow the container to be started with `--user` + if [ "$user" = '0' ]; then + find "$PGDATA" \! -user postgres -exec chown postgres '{}' + + find /var/run/postgresql \! -user postgres -exec chown postgres '{}' + + fi +} + +# initialize empty PGDATA directory with new database via 'initdb' +# arguments to `initdb` can be passed via POSTGRES_INITDB_ARGS or as arguments to this function +# `initdb` automatically creates the "postgres", "template0", and "template1" dbnames +# this is also where the database user is created, specified by `POSTGRES_USER` env +docker_init_database_dir() { + # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary + # see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html + local uid; uid="$(id -u)" + if ! getent passwd "$uid" &> /dev/null; then + # see if we can find a suitable "libnss_wrapper.so" (https://salsa.debian.org/sssd-team/nss-wrapper/-/commit/b9925a653a54e24d09d9b498a2d913729f7abb15) + local wrapper + for wrapper in {/usr,}/lib{/*,}/libnss_wrapper.so; do + if [ -s "$wrapper" ]; then + NSS_WRAPPER_PASSWD="$(mktemp)" + NSS_WRAPPER_GROUP="$(mktemp)" + export LD_PRELOAD="$wrapper" NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + local gid; gid="$(id -g)" + printf 'postgres:x:%s:%s:PostgreSQL:%s:/bin/false\n' "$uid" "$gid" "$PGDATA" > "$NSS_WRAPPER_PASSWD" + printf 'postgres:x:%s:\n' "$gid" > "$NSS_WRAPPER_GROUP" + break + fi + done + fi + + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then + set -- --waldir "$POSTGRES_INITDB_WALDIR" "$@" + fi + + # --pwfile refuses to handle a properly-empty file (hence the "\n"): https://github.com/docker-library/postgres/issues/1025 + eval 'initdb --username="$POSTGRES_USER" --pwfile=<(printf "%s\n" "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"' + + # unset/cleanup "nss_wrapper" bits + if [[ "${LD_PRELOAD:-}" == */libnss_wrapper.so ]]; then + rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" + unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + fi +} + +# print large warning if POSTGRES_PASSWORD is long +# error if both POSTGRES_PASSWORD is empty and POSTGRES_HOST_AUTH_METHOD is not 'trust' +# print large warning if POSTGRES_HOST_AUTH_METHOD is set to 'trust' +# assumes database is not set up, ie: [ -z "$DATABASE_ALREADY_EXISTS" ] +docker_verify_minimum_env() { + # check password first so we can output the warning before postgres + # messes it up + if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then + cat >&2 <<-'EOWARN' + + WARNING: The supplied POSTGRES_PASSWORD is 100+ characters. + + This will not work if used via PGPASSWORD with "psql". + + https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412) + https://github.com/docker-library/postgres/issues/507 + + EOWARN + fi + if [ -z "$POSTGRES_PASSWORD" ] && [ 'trust' != "$POSTGRES_HOST_AUTH_METHOD" ]; then + # The - option suppresses leading tabs but *not* spaces. :) + cat >&2 <<-'EOE' + Error: Database is uninitialized and superuser password is not specified. + You must specify POSTGRES_PASSWORD to a non-empty value for the + superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run". + + You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all + connections without a password. This is *not* recommended. + + See PostgreSQL documentation about "trust": + https://www.postgresql.org/docs/current/auth-trust.html + EOE + exit 1 + fi + if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then + cat >&2 <<-'EOWARN' + ******************************************************************************** + WARNING: POSTGRES_HOST_AUTH_METHOD has been set to "trust". This will allow + anyone with access to the Postgres port to access your database without + a password, even if POSTGRES_PASSWORD is set. See PostgreSQL + documentation about "trust": + https://www.postgresql.org/docs/current/auth-trust.html + In Docker's default configuration, this is effectively any other + container on the same system. + + It is not recommended to use POSTGRES_HOST_AUTH_METHOD=trust. Replace + it with "-e POSTGRES_PASSWORD=password" instead to set a password in + "docker run". + ******************************************************************************** + EOWARN + fi +} + +# usage: docker_process_init_files [file [file [...]]] +# ie: docker_process_init_files /always-initdb.d/* +# process initializer files, based on file extensions and permissions +docker_process_init_files() { + # psql here for backwards compatibility "${psql[@]}" + psql=( docker_process_sql ) + + printf '\n' + local f + for f; do + case "$f" in + *.sh) + # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936 + # https://github.com/docker-library/postgres/pull/452 + if [ -x "$f" ]; then + printf '%s: running %s\n' "$0" "$f" + "$f" + else + printf '%s: sourcing %s\n' "$0" "$f" + . "$f" + fi + ;; + *.sql) printf '%s: running %s\n' "$0" "$f"; docker_process_sql -f "$f"; printf '\n' ;; + *.sql.gz) printf '%s: running %s\n' "$0" "$f"; gunzip -c "$f" | docker_process_sql; printf '\n' ;; + *.sql.xz) printf '%s: running %s\n' "$0" "$f"; xzcat "$f" | docker_process_sql; printf '\n' ;; + *.sql.zst) printf '%s: running %s\n' "$0" "$f"; zstd -dc "$f" | docker_process_sql; printf '\n' ;; + *) printf '%s: ignoring %s\n' "$0" "$f" ;; + esac + printf '\n' + done +} + +# Execute sql script, passed via stdin (or -f flag of pqsl) +# usage: docker_process_sql [psql-cli-args] +# ie: docker_process_sql --dbname=mydb <<<'INSERT ...' +# ie: docker_process_sql -f my-file.sql +# ie: docker_process_sql > "$PGDATA/pg_hba.conf" +} + +# start socket-only postgresql server for setting up or running scripts +# all arguments will be passed along as arguments to `postgres` (via pg_ctl) +docker_temp_server_start() { + if [ "$1" = 'postgres' ]; then + shift + fi + + # internal start of server in order to allow setup using psql client + # does not listen on external TCP/IP and waits until start finishes + set -- "$@" -c listen_addresses='' -p "${PGPORT:-5432}" + + PGUSER="${PGUSER:-$POSTGRES_USER}" \ + pg_ctl -D "$PGDATA" \ + -o "$(printf '%q ' "$@")" \ + -w start +} + +# stop postgresql server after done setting up user and running scripts +docker_temp_server_stop() { + PGUSER="${PGUSER:-postgres}" \ + pg_ctl -D "$PGDATA" -m fast -w stop +} + +# check arguments for an option that would cause postgres to stop +# return true if there is one +_pg_want_help() { + local arg + for arg; do + case "$arg" in + # postgres --help | grep 'then exit' + # leaving out -C on purpose since it always fails and is unhelpful: + # postgres: could not access the server configuration file "/var/lib/postgresql/data/postgresql.conf": No such file or directory + -'?'|--help|--describe-config|-V|--version) + return 0 + ;; + esac + done + return 1 +} + +_main() { + # if first arg looks like a flag, assume we want to run postgres server + if [ "${1:0:1}" = '-' ]; then + set -- postgres "$@" + fi + + if [ "$1" = 'postgres' ] && ! _pg_want_help "$@"; then + docker_setup_env + # setup data directories and permissions (when run as root) + docker_create_db_directories + if [ "$(id -u)" = '0' ]; then + # then restart script as postgres user + exec gosu postgres "$BASH_SOURCE" "$@" + fi + + # only run initialization on an empty data directory + if [ -z "$DATABASE_ALREADY_EXISTS" ]; then + docker_verify_minimum_env + + # check dir permissions to reduce likelihood of half-initialized database + ls /docker-entrypoint-initdb.d/ > /dev/null + + docker_init_database_dir + pg_setup_hba_conf "$@" + + # PGPASSWORD is required for psql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless + # e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS + export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}" + docker_temp_server_start "$@" + + docker_setup_db + docker_process_init_files /docker-entrypoint-preinitdb.d/* + docker_temp_server_stop + docker_temp_server_start "$@" + docker_process_init_files /docker-entrypoint-initdb.d/* + + docker_temp_server_stop + unset PGPASSWORD + + cat <<-'EOM' + + PostgreSQL init process complete; ready for start up. + + EOM + else + cat <<-'EOM' + + PostgreSQL Database directory appears to contain a database; Skipping initialization + + EOM + fi + fi + + exec "$@" +} + +if ! _is_sourced; then + _main "$@" +fi diff --git a/docker/pgstac/dbinit/pgstac-rust-preinit.sh b/docker/pgstac/dbinit/pgstac-rust-preinit.sh new file mode 100755 index 00000000..91b734c2 --- /dev/null +++ b/docker/pgstac/dbinit/pgstac-rust-preinit.sh @@ -0,0 +1,3 @@ +psql -X -q -v ON_ERROR_STOP=1 < /dev/null && pwd ) +cd $SCRIPT_DIR/../../../src function usage() { echo -n \ diff --git a/scripts/bin/initpgstac b/docker/pypgstac/bin/initpgstac similarity index 58% rename from scripts/bin/initpgstac rename to docker/pypgstac/bin/initpgstac index e937eded..b205b7cf 100755 --- a/scripts/bin/initpgstac +++ b/docker/pypgstac/bin/initpgstac @@ -1,11 +1,11 @@ #!/bin/bash -if [ ! $PGSTACDOCKER == 1 ]; then - echo "This script should only be run within pgstac docker"; exit 1; -fi -cd /opt/src/pgstac +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR/../../../src/pgstac + psql -X -q -v ON_ERROR_STOP=1 < /dev/null && pwd ) +cd $SCRIPT_DIR/../../../src/pgstac psql -f pgstac.sql psql -v ON_ERROR_STOP=1 <<-EOSQL \copy collections (content) FROM 'tests/testdata/collections.ndjson' diff --git a/docker/pypgstac/bin/makemigration b/docker/pypgstac/bin/makemigration new file mode 100755 index 00000000..6837ba2c --- /dev/null +++ b/docker/pypgstac/bin/makemigration @@ -0,0 +1,143 @@ +#!/bin/bash +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SRCDIR=$SCRIPT_DIR/../../../src +cd $SRCDIR + +SHORT=f:,t:,o,d,h +LONG=from:,to:,overwrite,debug,help +OPTS=$(getopt --alternative --name $0 --options $SHORT --longoptions $LONG -- "$@") + +eval set -- "$OPTS" + +while : +do + case "$1" in + -f | --from ) + FROM="$2" + shift 2 + ;; + -t | --to ) + TO="$2" + shift 2 + ;; + -o | --overwrite ) + OVERWRITE=1 + shift 1 + ;; + -d | --debug ) + DEBUG=1 + shift 1 + ;; + -h | --help) + "Help" + exit 2 + ;; + --) + shift; + break + ;; + *) + echo "Unexpected option: $1" + ;; + esac +done + +# make sure that from and to exist + + + +BASEDIR=$SRCDIR +PYPGSTACDIR=$BASEDIR/pypgstac +MIGRATIONSDIR=$BASEDIR/pgstac/migrations +SQLDIR=$BASEDIR/pgstac/sql + +# Check if from SQL file exists +FROMSQL=$MIGRATIONSDIR/pgstac.$FROM.sql +if [ -f $FROMSQL ]; then + echo "Migrating From: $FROMSQL" +else + echo "From SQL $FROMSQL does not exist" + exit 1 +fi + +# Check if to SQL file exists +TOSQL=$MIGRATIONSDIR/pgstac.$TO.sql +if [ -f $TOSQL ]; then + echo "Migrating To: $TOSQL" +else + echo "To SQL $TOSQL does not exist" + exit 1 +fi + +MIGRATIONSQL=$MIGRATIONSDIR/pgstac.$FROM-$TO.sql +if [[ -f "$MIGRATIONSQL" ]]; then + if [[ "$OVERWRITE" != 1 ]]; then + echo "$MIGRATIONSQL Already exists." + select yn in "Yes" "No"; do + case $yn in + Yes ) break;; + No ) exit 1;; + esac + done + else + echo "Removing existing $MIGRATIONSQL" + rm $MIGRATIONSQL + fi +else + echo "Creating $MIGRATIONSQL" +fi + +pg_isready -t 10 +# Create Databases to inspect to create migration +psql -q >/dev/null 2>&1 <<-'EOSQL' + DROP DATABASE IF EXISTS migra_from; + CREATE DATABASE migra_from; + DROP DATABASE IF EXISTS migra_to; + CREATE DATABASE migra_to; +EOSQL + +TODBURL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST:-localhost}:${PGPORT:-5432}/migra_to" +FROMDBURL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST:-localhost}:${PGPORT:-5432}/migra_from" + +# Make sure to clean up migra databases +function drop_migra_dbs(){ +psql -q >/dev/null 2>&1 <<-'EOSQL' + DROP DATABASE IF EXISTS migra_from; + DROP DATABASE IF EXISTS migra_to; +EOSQL +} + +trap drop_migra_dbs 0 2 3 15 + +echo "Creating Migration from $FROM to $TO" + +# Install From into Database +psql -q -X -1 -v ON_ERROR_STOP=1 -v CLIENT_MIN_MESSAGES=WARNING -f $FROMSQL $FROMDBURL >/dev/null || exit 1; + +# Install To into Database +psql -q -X -1 -v ON_ERROR_STOP=1 -v CLIENT_MIN_MESSAGES=WARNING -f $TOSQL $TODBURL >/dev/null || exit 1; + + +# Calculate the migration +MIGRATION=$(mktemp) +trap "rm $MIGRATION" 0 2 3 15 + +migra --schema pgstac --unsafe $FROMDBURL $TODBURL >$MIGRATION +if [[ $DEBUG == 1 ]]; then + echo "*************" + cat $MIGRATION + echo "*************" +fi + +# Append wrapper around created migration with idempotent and transaction statements + +echo "SET client_min_messages TO WARNING;" >$MIGRATIONSQL +echo "SET SEARCH_PATH to pgstac, public;" >>$MIGRATIONSQL +cat $SQLDIR/000_idempotent_pre.sql >>$MIGRATIONSQL +echo "-- BEGIN migra calculated SQL" >>$MIGRATIONSQL +cat $MIGRATION >>$MIGRATIONSQL +echo "-- END migra calculated SQL" >>$MIGRATIONSQL +cat $SQLDIR/998_idempotent_post.sql $SQLDIR/999_version.sql >>$MIGRATIONSQL + +echo "Migration created at $MIGRATIONSQL." +exit 0 diff --git a/scripts/bin/resetpgstac b/docker/pypgstac/bin/resetpgstac similarity index 67% rename from scripts/bin/resetpgstac rename to docker/pypgstac/bin/resetpgstac index 29c01839..097c3d5b 100755 --- a/scripts/bin/resetpgstac +++ b/docker/pypgstac/bin/resetpgstac @@ -1,10 +1,7 @@ #!/bin/bash -if [ ! $PGSTACDOCKER == 1 ]; then - echo "This script should only be run within pgstac docker"; exit 1; -fi -cd /opt/src/pgstac +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR/../../../src/pgstac set -e -psql -f pgstac.sql psql -v ON_ERROR_STOP=1 <<-EOSQL DROP SCHEMA IF EXISTS pgstac CASCADE; \i pgstac.sql diff --git a/docker/pypgstac/bin/stageversion b/docker/pypgstac/bin/stageversion new file mode 100755 index 00000000..3f89d452 --- /dev/null +++ b/docker/pypgstac/bin/stageversion @@ -0,0 +1,54 @@ +#!/bin/bash +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SRCDIR=$SCRIPT_DIR/../../../src +cd $SRCDIR +BASEDIR=$SRCDIR +SQLDIR=$BASEDIR/pgstac/sql +PYPGSTACDIR=$BASEDIR/pypgstac +MIGRATIONSDIR=$BASEDIR/pgstac/migrations + + +# Remove any existing unreleased migrations +find $MIGRATIONSDIR -name "*unreleased*" -exec rm {} \; + +# Get Version +if [[ ! -z "$1" ]]; then + VERSION=$1 + if echo "$VERSION" | grep -E "^[0-9]+[.][0-9]+[.][0-9]+$"; then + echo "STAGING VERSION: $VERSION" + else + echo "Version must be in the format 0.1.2" + exit 1 + fi + git tag -f "v$VERSION" +else + VERSION="unreleased" +fi + + +OLDVERSION=$(find $MIGRATIONSDIR -name "pgstac.*.sql" | sed -En 's/^.*pgstac\.([0-9]+\.[0-9]+\.[0-9]+)\.sql$/\1/p' | grep -v "$VERSION" | sort -Vr | head -1) + + + +echo "Bumping version from $OLDVERSION to $VERSION" + +# Assemble a base migration for the version and put it in the migrations directory. +cd $SQLDIR +echo "SELECT set_version('${VERSION}');" >999_version.sql +cat *.sql >$MIGRATIONSDIR/pgstac.${VERSION}.sql +cd $BASEDIR/pgstac + +# make the base pgstac.sql a symbolic link to the most recent version +rm pgstac.sql +ln -s migrations/pgstac.${VERSION}.sql pgstac.sql + +# Update the version number in the appropriate places +[[ $VERSION == 'unreleased' ]] && PYVERSION="${OLDVERSION}-dev" || PYVERSION="$VERSION" +echo "Setting pypgstac version to $PYVERSION" +cat < $PYPGSTACDIR/python/pypgstac/version.py +"""Version.""" +__version__ = "${PYVERSION}" +EOD +sed -i "s/^version[ ]*=[ ]*.*/version = \"${PYVERSION}\"/" $PYPGSTACDIR/pyproject.toml + +makemigration -f $OLDVERSION -t $VERSION diff --git a/scripts/bin/test b/docker/pypgstac/bin/test similarity index 70% rename from scripts/bin/test rename to docker/pypgstac/bin/test index d93b01e1..f6862b8f 100755 --- a/scripts/bin/test +++ b/docker/pypgstac/bin/test @@ -1,14 +1,12 @@ #!/bin/bash set -e +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +export SRCDIR=$SCRIPT_DIR/../../../src +export PGSTACDIR=$SRCDIR/pgstac if [[ "${CI}" ]]; then set -x fi -if [ ! $PGSTACDOCKER == 1 ]; then - echo "This script should only be run within pgstac docker"; exit 1; -fi - -source $(dirname $0)/tmpdb function usage() { echo -n \ @@ -19,37 +17,52 @@ This scripts is meant to be run inside the dev container. " } +function setuptestdb(){ + cd $PGSTACDIR + psql -X -q -v ON_ERROR_STOP=1 < /dev/null && pwd ) @@ -55,3 +47,8 @@ ALTER DATABASE pgstac_test_db_template SET SEARCH_PATH to pgstac, public; EOSQL } export -f setuptestdb + +function tmpdb(){ + TMPFILE=$(mktemp -t tmpdb_XXXXX) + trap "rm $TMPFILE" 0 2 3 15 + TMPDB=$(basename $TMPFILE) diff --git a/scripts/bin/README b/scripts/bin/README deleted file mode 100644 index 5359a42f..00000000 --- a/scripts/bin/README +++ /dev/null @@ -1 +0,0 @@ -scripts/bin contains scripts that are meant to be run within the docker container diff --git a/scripts/bin/stageversion b/scripts/bin/stageversion deleted file mode 100755 index 55ef0bcc..00000000 --- a/scripts/bin/stageversion +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/bash -set -e - -if [[ "${CI}" ]]; then - set -x -fi -if [ ! $PGSTACDOCKER == 1 ]; then - echo "This script should only be run within pgstac docker"; exit 1; -fi - -source $(dirname $0)/tmpdb -setuptestdb - -BASEDIR=/opt/src -PYPGSTACDIR=$BASEDIR/pypgstac -MIGRATIONSDIR=$BASEDIR/pgstac/migrations - - -function usage() { - echo -n \ - "Usage: $(basename "$0"). VERSION FORCE - -This scripts is meant to be run inside the dev container. - -" -} -VERSION=$1 -FORCE=$2 -[ -n "${FORCE}" ] && echo "Rewriting last migration step directly" || echo "Creating staging file for last migration." -if [[ -z "${VERSION}" ]]; then - echo "ERROR: Must supply a version." - usage - exit 1 -fi - - -TODBURL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST:-localhost}:${PGPORT:-5432}/migra_to" -FROMDBURL="postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST:-localhost}:${PGPORT:-5432}/migra_from" - -echo $TODBURL -export | grep PG - -function base_migrations(){ - find $MIGRATIONSDIR -regex ".*\/pgstac\.[0-9]+\.[0-9]+\.[0-9]+\.sql" -} - -function base_migration_versions(){ - base_migrations | sed -En 's/.*pgstac\.([0-9]+\.[0-9]+\.[0-9]+)\.sql/\1/p' | sort -V -} - -function create_migra_dbs(){ -psql -q >/dev/null 2>&1 <<-'EOSQL' - DROP DATABASE IF EXISTS migra_from; - CREATE DATABASE migra_from; - DROP DATABASE IF EXISTS migra_to; - CREATE DATABASE migra_to; - DROP DATABASE IF EXISTS base_test; - CREATE DATABASE base_test; -EOSQL -} - -function drop_migra_dbs(){ -psql -q >/dev/null 2>&1 <<-'EOSQL' - DROP DATABASE IF EXISTS migra_from; - DROP DATABASE IF EXISTS migra_to; -EOSQL -} - -function pgwait(){ - RETRIES=10 - until pg_isready >/dev/null 2>&1 || [ $RETRIES -eq 0 ]; do - sleep 1 - done -} - -function calc_migration(){ - cd $MIGRATIONS_DIR - tmpfile=$(mktemp) - trap "rm -f $tmpfile" 0 2 3 15 - MIGRA_FROM_FILE=$1 - MIGRA_TO_FILE=$2 - pgwait - create_migra_dbs - trap drop_migra_dbs 0 2 3 15 - - psql -q -X -1 -v ON_ERROR_STOP=1 -v CLIENT_MIN_MESSAGES=WARNING -f $MIGRA_FROM_FILE $FROMDBURL >/dev/null || exit 1; - psql -q -X -1 -v ON_ERROR_STOP=1 -v CLIENT_MIN_MESSAGES=WARNING -f $MIGRA_TO_FILE $TODBURL >/dev/null || exit 1; - - migra --schema pgstac --unsafe $FROMDBURL $TODBURL || echo "" -} - -cd $BASEDIR/pgstac/sql - -echo "SELECT set_version('${VERSION}');" >999_version.sql - -# Assemble a base migration for the version and put it in the migrations directory. -cat *.sql >$MIGRATIONSDIR/pgstac.${VERSION}.sql - - -cat < $PYPGSTACDIR/pypgstac/version.py -"""Version.""" -__version__ = "${VERSION}" -EOD - - -# Get Array of available base migration files -readarray -t VERSIONS < <(base_migration_versions) - -# Calculate incremental versions sql migrations -cnt=$((${#VERSIONS[@]}-1)) -for (( i=0; i<$cnt; i++ )); do - F=${VERSIONS[$i]} - F_BASE="$MIGRATIONSDIR/pgstac.${F}.sql" - T=${VERSIONS[$i+1]} - T_BASE="$MIGRATIONSDIR/pgstac.${T}.sql" - - FILE="$MIGRATIONSDIR/pgstac.${F}-${T}.sql" - [ -n "${FORCE}" -a -f "$FILE" -a $(( $i + 1 )) -eq $cnt ] && rm $FILE - STAGED="$FILE.staged" - STAGEDINIT="$STAGED.init" - trap "rm $STAGEDINIT" 0 2 3 15 - - if [ -f $FILE ]; then - echo "Migration $FILE already exists." - else - echo "Creating migrations from $F to $T" - echo "SET client_min_messages TO WARNING;" >$STAGEDINIT - echo "SET SEARCH_PATH to pgstac, public;" >>$STAGEDINIT - echo "-- BEGIN migra calculated SQL" >>$STAGEDINIT - calc_migration $F_BASE $T_BASE >>$STAGEDINIT - echo "-- END migra calculated SQL" >>$STAGEDINIT - cd /opt/src/pgstac/sql - cat 000_idempotent_pre.sql $STAGEDINIT 998_idempotent_post.sql 999_version.sql >$STAGED - rm $STAGEDINIT - echo "Created '$STAGED'. You must review and rename to $FILE before" - echo "committing and tagging a release." - fi - [ -n "${FORCE}" -a -f "$STAGED" ] && mv $STAGED $FILE -done -exit 0 diff --git a/scripts/console b/scripts/console index 84e3a1d2..60068e4b 100755 --- a/scripts/console +++ b/scripts/console @@ -30,15 +30,15 @@ while [[ "$#" > 0 ]]; do case $1 in if [ "${BASH_SOURCE[0]}" = "${0}" ]; then - docker-compose up -d + docker compose up -d if [[ "${DB_CONSOLE}" ]]; then - docker-compose exec pgstac psql + docker compose exec pgstac psql exit 0 fi - docker-compose exec pgstac /bin/bash + docker compose exec pgstac /bin/bash fi diff --git a/scripts/format b/scripts/format index 5c687331..0a47433a 100755 --- a/scripts/format +++ b/scripts/format @@ -1,21 +1,4 @@ #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd $SCRIPT_DIR/.. -set -e - -if [[ "${CI}" ]]; then - set -x -fi - -function usage() { - echo -n \ - "Usage: $(basename "$0") -Format code in this project - -" -} - -if [ "${BASH_SOURCE[0]}" = "${0}" ]; then - echo "Formatting pypgstac..." - docker-compose run --rm pgstac sh -c "ruff --fix pypgstac/pypgstac; ruff --fix pypgstac/tests" -fi +$SCRIPT_DIR/runinpypgstac format "$@" diff --git a/scripts/migrate b/scripts/migrate index f166d7d6..445a5179 100755 --- a/scripts/migrate +++ b/scripts/migrate @@ -1,24 +1,4 @@ #!/bin/bash - -set -e - -if [[ "${CI}" ]]; then - set -x -fi - -function usage() { - echo -n \ - "Usage: $(basename "$0") -Run migrations against the development database. -" -} - -if [ "${BASH_SOURCE[0]}" = "${0}" ]; then - - # Run database migrations - docker-compose up -d pgstac - docker-compose \ - exec pgstac \ - bash -c "pypgstac pgready && pypgstac migrate --debug" - -fi +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR/.. +$SCRIPT_DIR/runinpypgstac pypgstac migrate "$@" diff --git a/scripts/pgstacenv b/scripts/pgstacenv new file mode 100644 index 00000000..264c3725 --- /dev/null +++ b/scripts/pgstacenv @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR/.. + +export PATH=$SCRIPT_DIR:$PATH + +set -e + +if [[ "${CI}" ]]; then + set -x +fi diff --git a/scripts/runinpypgstac b/scripts/runinpypgstac new file mode 100755 index 00000000..b9869f3c --- /dev/null +++ b/scripts/runinpypgstac @@ -0,0 +1,40 @@ +#!/bin/bash +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR/.. +set -e + +if [[ "${CI}" ]]; then + set -x +fi +function usage() { + echo -n \ + "Usage: $(basename "$0") <--build> <--no-cache>