diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..213f1f7 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# PostgreSQL Configuration +POSTGRES_PASSWORD=changeme +POSTGRES_DB=app +POSTGRES_USER=postgres +POSTGRES_PORT=5432 + +# Oracle Configuration (if using Oracle container) +# ORACLE_PASSWORD=OraclePassword123 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e12f663 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,45 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Bug Description + + +## Environment +- PostgreSQL Version: +- Oracle Version: +- Oracle Instant Client Version: +- Container Platform: +- Architecture: + +## Steps to Reproduce +1. +2. +3. + +## Expected Behavior + + +## Actual Behavior + + +## Error Messages/Logs +``` + +``` + +## Configuration + +```yaml +# Paste configuration here +``` + +## Additional Context + + +## Possible Solution + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e7f6e76 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,39 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Feature Description + + +## Problem Statement + +Is your feature request related to a problem? Please describe. + +## Proposed Solution + + +## Alternative Solutions + + +## Use Case + + +## PostgreSQL Versions + +- [ ] PostgreSQL 16 +- [ ] PostgreSQL 17 +- [ ] PostgreSQL 18 +- [ ] All versions + +## Additional Context + + +## Impact + + +## Implementation Ideas + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4b692fc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,44 @@ +## Description + + +## Type of Change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Performance improvement +- [ ] Code refactoring +- [ ] CI/CD changes + +## PostgreSQL Versions Affected + +- [ ] PostgreSQL 16 +- [ ] PostgreSQL 17 +- [ ] PostgreSQL 18 +- [ ] All versions + +## Testing + +- [ ] Built Docker image successfully +- [ ] Tested with docker-compose +- [ ] Verified extensions load correctly (oracle_fdw, pg_cron, anon) +- [ ] Tested Oracle FDW connectivity +- [ ] Added/updated automated tests +- [ ] Tested on multiple architectures (amd64/arm64) + +## Checklist +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings or errors +- [ ] I have tested my changes locally +- [ ] Any dependent changes have been merged and published + +## Related Issues + +Closes # + +## Additional Notes + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0e6bacd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "chore(deps)" + include: "scope" + + # Docker base images + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 3 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore(deps)" + include: "scope" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index db8a0a0..27f5c26 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -3,17 +3,39 @@ name: Build and Push Docker Image on: push: branches: [ "main" ] - # pull_request: - # branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +permissions: + contents: read + packages: write jobs: docker: runs-on: ubuntu-latest + strategy: + matrix: + pg_version: + - major: "16" + version: "16.6" + latest: false + oracle_fdw_version: "ORACLE_FDW_2_7_0" + - major: "17" + version: "17.1.5" + latest: false + oracle_fdw_version: "ORACLE_FDW_2_7_0" + - major: "18" + version: "18.4" + latest: true + oracle_fdw_version: "ORACLE_FDW_2_8_0" + oracle_version: ["19.25.0.0.0"] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Login to Docker Hub + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} @@ -25,20 +47,8 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build and push to Docker - uses: docker/build-push-action@v6 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:17.1.5 - build-args: | - PG_MAJOR=17 - ORACLE_VERSION=19.25.0.0.0 - # tags: | - # ${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:latest - # ${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:v1.0.0-rc.1 - - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ghcr.io @@ -51,18 +61,147 @@ jobs: GITHUB_REPOSITORY=${{github.repository_owner}} && echo "repository=${GITHUB_REPOSITORY@L}/postgres-container" >> $GITHUB_OUTPUT - # - name: Push image to GHCR - # run: | - # docker buildx imagetools create \ - # --tag ghcr.io/${{ steps.lower-repo.outputs.repository }}:v0.0.1 \ - # ${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:v0.0.1 + - name: Generate Docker tags + id: docker-tags + run: | + TAGS="${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:${{ matrix.pg_version.version }}" + TAGS="${TAGS},${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:${{ matrix.pg_version.major }}" + if [ "${{ matrix.pg_version.latest }}" == "true" ]; then + TAGS="${TAGS},${{ secrets.DOCKERHUB_USERNAME }}/postgres-container:latest" + fi + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - name: Generate GHCR tags + id: ghcr-tags + run: | + TAGS="ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ matrix.pg_version.version }}" + TAGS="${TAGS},ghcr.io/${{ steps.lower-repo.outputs.repository }}:${{ matrix.pg_version.major }}" + if [ "${{ matrix.pg_version.latest }}" == "true" ]; then + TAGS="${TAGS},ghcr.io/${{ steps.lower-repo.outputs.repository }}:latest" + fi + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - name: Build and push to Docker Hub + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.docker-tags.outputs.tags }} + build-args: | + PG_MAJOR=${{ matrix.pg_version.major }} + PG_VERSION=${{ matrix.pg_version.version }} + ORACLE_VERSION=${{ matrix.oracle_version }} + ORACLE_FDW_VERSION=${{ matrix.pg_version.oracle_fdw_version }} + cache-from: type=gha + cache-to: type=gha,mode=max - name: Build and push to GHCR uses: docker/build-push-action@v6 with: context: . + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} - tags: ghcr.io/${{ steps.lower-repo.outputs.repository }}:17.1.5 + tags: ${{ steps.ghcr-tags.outputs.tags }} build-args: | - PG_MAJOR=17 - ORACLE_VERSION=19.25.0.0.0 \ No newline at end of file + PG_MAJOR=${{ matrix.pg_version.major }} + PG_VERSION=${{ matrix.pg_version.version }} + ORACLE_VERSION=${{ matrix.oracle_version }} + ORACLE_FDW_VERSION=${{ matrix.pg_version.oracle_fdw_version }} + cache-from: type=gha + cache-to: type=gha,mode=max + + test: + runs-on: ubuntu-latest + needs: docker + if: github.event_name == 'pull_request' + strategy: + matrix: + pg_version: + - major: "16" + version: "16.6" + oracle_fdw_version: "ORACLE_FDW_2_7_0" + - major: "17" + version: "17.1.5" + oracle_fdw_version: "ORACLE_FDW_2_7_0" + - major: "18" + version: "18.4" + oracle_fdw_version: "ORACLE_FDW_2_8_0" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build test image + uses: docker/build-push-action@v6 + with: + context: . + load: true + tags: postgres-oracle-fdw:test + build-args: | + PG_MAJOR=${{ matrix.pg_version.major }} + PG_VERSION=${{ matrix.pg_version.version }} + ORACLE_VERSION=19.25.0.0.0 + ORACLE_FDW_VERSION=${{ matrix.pg_version.oracle_fdw_version }} + + - name: Test PostgreSQL starts + run: | + docker run -d --name postgres-test \ + -e POSTGRES_PASSWORD=testpass \ + postgres-oracle-fdw:test + + # Wait for PostgreSQL to be ready + timeout=60 + while [ $timeout -gt 0 ]; do + if docker exec postgres-test pg_isready -U postgres; then + echo "PostgreSQL is ready!" + break + fi + sleep 2 + timeout=$((timeout - 2)) + done + + if [ $timeout -le 0 ]; then + echo "PostgreSQL failed to start" + docker logs postgres-test + exit 1 + fi + + - name: Test extensions + run: | + # Test oracle_fdw extension + docker exec postgres-test psql -U postgres -c "CREATE EXTENSION oracle_fdw;" || { + echo "Failed to create oracle_fdw extension" + exit 1 + } + + # Test pg_cron extension + docker exec postgres-test psql -U postgres -c "CREATE EXTENSION pg_cron;" || { + echo "Failed to create pg_cron extension" + exit 1 + } + + # Test anon extension + docker exec postgres-test psql -U postgres -c "CREATE EXTENSION anon;" || { + echo "Failed to create anon extension" + exit 1 + } + + echo "All extensions loaded successfully!" + + - name: Verify extension versions + run: | + docker exec postgres-test psql -U postgres -c " + SELECT name, default_version, installed_version + FROM pg_available_extensions + WHERE name IN ('oracle_fdw', 'pg_cron', 'anon') + ORDER BY name; + " + + - name: Cleanup + if: always() + run: | + docker stop postgres-test || true + docker rm postgres-test || true \ No newline at end of file diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..dd7aaea --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,66 @@ +name: Security Scan + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + # Run weekly on Mondays at 00:00 UTC + - cron: '0 0 * * 1' + workflow_dispatch: + +permissions: + contents: read + security-events: write + +jobs: + trivy-scan: + runs-on: ubuntu-latest + strategy: + matrix: + pg_version: + - major: "18" + version: "18.4" + oracle_fdw_version: "ORACLE_FDW_2_8_0" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build test image + uses: docker/build-push-action@v6 + with: + context: . + load: true + tags: postgres-oracle-fdw:scan + build-args: | + PG_MAJOR=${{ matrix.pg_version.major }} + PG_VERSION=${{ matrix.pg_version.version }} + ORACLE_VERSION=19.25.0.0.0 + ORACLE_FDW_VERSION=${{ matrix.pg_version.oracle_fdw_version }} + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: 'postgres-oracle-fdw:scan' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + exit-code: '0' + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Run Trivy vulnerability scanner (table format) + uses: aquasecurity/trivy-action@master + with: + image-ref: 'postgres-oracle-fdw:scan' + format: 'table' + severity: 'CRITICAL,HIGH,MEDIUM' + exit-code: '0' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8295726 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,242 @@ +# Contributing to CloudNative PostgreSQL Extension + +Thank you for your interest in contributing to this project! This document provides guidelines and instructions for contributing. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [Making Changes](#making-changes) +- [Testing](#testing) +- [Submitting Changes](#submitting-changes) +- [Reporting Issues](#reporting-issues) + +## Code of Conduct + +We are committed to providing a welcoming and inclusive environment for all contributors. Please: + +- Be respectful and considerate +- Welcome newcomers and help them get started +- Focus on what is best for the community +- Show empathy towards other community members + +## Getting Started + +1. **Fork the repository** on GitHub +2. **Clone your fork** locally: + ```bash + git clone https://github.com/YOUR_USERNAME/cloudnative-pg-extension.git + cd cloudnative-pg-extension + ``` +3. **Add upstream remote**: + ```bash + git remote add upstream https://github.com/dingo4dev/cloudnative-pg-extension.git + ``` + +## Development Setup + +### Prerequisites + +- Docker (20.10 or later) +- Docker Compose +- Git +- Basic knowledge of PostgreSQL and Docker + +### Building Locally + +Use the provided build script: + +```bash +# Build all versions +./build-versions.sh all + +# Build specific version +./build-versions.sh 18 +``` + +Or use docker-compose: + +```bash +docker-compose build +``` + +### Testing Locally + +```bash +# Start the stack +docker-compose up -d + +# Check logs +docker-compose logs -f + +# Connect to PostgreSQL +docker-compose exec postgres psql -U postgres -d app + +# Test extensions +docker-compose exec postgres psql -U postgres -d app -c "\dx" +``` + +## Making Changes + +### Branch Naming Convention + +Create a branch with a descriptive name: + +- `feature/add-new-extension` - for new features +- `fix/oracle-fdw-connection` - for bug fixes +- `docs/update-readme` - for documentation +- `chore/update-dependencies` - for maintenance tasks + +```bash +git checkout -b feature/your-feature-name +``` + +### Code Style + +- **Dockerfile**: Follow Docker best practices + - Use multi-stage builds where appropriate + - Minimize layers + - Clean up in the same RUN command + - Pin versions for reproducibility + +- **Shell Scripts**: Follow bash best practices + - Use `set -e` for error handling + - Quote variables + - Add comments for complex logic + +- **YAML**: Use 2-space indentation + +- **Markdown**: Follow standard markdown formatting + +### Commit Messages + +Write clear, concise commit messages: + +``` +type: brief description + +Longer description if needed, explaining what and why, +not how (the code shows how). + +Closes #123 +``` + +Types: +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `chore`: Maintenance tasks +- `refactor`: Code refactoring +- `test`: Adding or updating tests +- `ci`: CI/CD changes + +Examples: +``` +feat: add PostgreSQL 16 support + +docs: update README with multi-arch build instructions + +fix: correct oracle_fdw version pinning in Dockerfile +``` + +## Testing + +### Required Tests + +Before submitting a PR, ensure: + +1. **Build succeeds** for all PostgreSQL versions: + ```bash + ./build-versions.sh all + ``` + +2. **Extensions load correctly**: + ```bash + docker run --rm -e POSTGRES_PASSWORD=test postgres-oracle-fdw:18.4 \ + postgres -c "shared_preload_libraries='oracle_fdw,pg_cron'" + ``` + +3. **Docker compose works**: + ```bash + docker-compose up -d + docker-compose exec postgres psql -U postgres -c "CREATE EXTENSION oracle_fdw;" + docker-compose down + ``` + +### Automated Tests + +GitHub Actions will automatically: +- Build all PostgreSQL versions +- Test extension loading +- Run security scans +- Check for multi-architecture compatibility + +## Submitting Changes + +1. **Update your branch** with the latest upstream: + ```bash + git fetch upstream + git rebase upstream/main + ``` + +2. **Push to your fork**: + ```bash + git push origin feature/your-feature-name + ``` + +3. **Create a Pull Request** on GitHub: + - Use the PR template + - Provide a clear description + - Link related issues + - Mark the PR as draft if it's work in progress + +4. **Address review feedback**: + - Make requested changes + - Push updates to your branch + - Respond to comments + +## Reporting Issues + +### Bug Reports + +Use the bug report template and include: +- PostgreSQL version +- Oracle version +- Container platform (Docker, Kubernetes, etc.) +- Architecture (amd64, arm64) +- Steps to reproduce +- Expected vs actual behavior +- Error messages/logs +- Configuration files (if relevant) + +### Feature Requests + +Use the feature request template and include: +- Clear description of the feature +- Use case and benefits +- Proposed implementation (if you have ideas) + +## Documentation + +When making changes that affect users: + +1. **Update README.md** with new features or changes +2. **Add examples** in the `examples/` directory +3. **Update tutorials** if changing core functionality +4. **Add inline comments** for complex code + +## Questions? + +If you have questions: +- Open an issue with the "question" label +- Check existing issues and discussions +- Review the README and examples + +## Recognition + +Contributors will be recognized in: +- GitHub contributors page +- Release notes (for significant contributions) + +Thank you for contributing! 🎉 diff --git a/Dockerfile b/Dockerfile index f423dae..64fac48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,30 @@ # Reference: https://cloudnative-pg.io/blog/creating-container-images/ -ARG PG_MAJOR=17 +ARG PG_MAJOR=18 +ARG PG_VERSION=18.4 FROM ghcr.io/cloudnative-pg/postgresql:$PG_MAJOR-bullseye +ARG PG_MAJOR +ARG PG_VERSION +ARG ORACLE_VERSION=19.25.0.0.0 +ARG ORACLE_FDW_VERSION=ORACLE_FDW_2_8_0 + LABEL maintainer="dingo4dev " LABEL org.opencontainers.image.title="CloudNative PostgreSQL with Oracle Integration" -LABEL org.opencontainers.image.description="CloudNative PostgreSQL 17 container with Oracle integration support (Oracle version 19.25.0.0.0)" -LABEL org.opencontainers.image.version="17.1.5" +LABEL org.opencontainers.image.description="CloudNative PostgreSQL ${PG_MAJOR} (${PG_VERSION}) container with Oracle integration support (Oracle version ${ORACLE_VERSION}, oracle_fdw ${ORACLE_FDW_VERSION})" +LABEL org.opencontainers.image.version="${PG_VERSION}" LABEL org.opencontainers.image.vendor="dingo4dev" LABEL org.opencontainers.image.licenses="GNU3" LABEL org.opencontainers.image.source="https://github.com/dingo4dev/cloudnative-pg-extension" - -ARG ORACLE_VERSION=19.25.0.0.0 +LABEL org.opencontainers.image.oracle_version="${ORACLE_VERSION}" +LABEL org.opencontainers.image.oracle_fdw_version="${ORACLE_FDW_VERSION}" USER root -RUN echo Postgresql Major Version: $PG_MAJOR && echo Oracle instant client version: $ORACLE_VERSION +RUN echo Postgresql Major Version: $PG_MAJOR && echo Oracle instant client version: $ORACLE_VERSION && echo Oracle FDW version: $ORACLE_FDW_VERSION # Install build dependencies -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --fix-missing \ build-essential \ git \ wget \ @@ -56,14 +62,13 @@ ENV ORACLE_HOME=/opt/oracle/instantclient_19_25 ENV LD_LIBRARY_PATH=$ORACLE_HOME ENV PATH=$ORACLE_HOME:$PATH -# Clone and build oracle_fdw +# Clone and build oracle_fdw (pinned to stable version) +ARG ORACLE_FDW_VERSION=ORACLE_FDW_2_8_0 RUN git clone https://github.com/laurenz/oracle_fdw.git \ - && cd oracle_fdw \ + && cd oracle_fdw \ + && git checkout ${ORACLE_FDW_VERSION} \ && make && make install -# RUN make ORACLE_HOME=$ORACLE_HOME \ -# && make install - # Add extension to postgresql.conf RUN echo "shared_preload_libraries = 'oracle_fdw,pg_cron'" >> /usr/share/postgresql/postgresql.conf.sample RUN echo "cron.database_name = 'app'" >> /usr/share/postgresql/postgresql.conf.sample @@ -76,4 +81,9 @@ RUN apt-get remove -y build-essential git postgresql-server-dev-$PG_MAJOR \ # Change the uid of postgres to 26 RUN usermod -u 26 postgres + +# Add health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD pg_isready -U postgres || exit 1 + USER 26 diff --git a/README.md b/README.md index f203d07..e6ef708 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,185 @@ # PostgreSQL Docker Image with Oracle FDW Support +[![Build and Push Docker Image](https://github.com/dingo4dev/cloudnative-pg-extension/actions/workflows/docker-build.yml/badge.svg)](https://github.com/dingo4dev/cloudnative-pg-extension/actions/workflows/docker-build.yml) +[![Security Scan](https://github.com/dingo4dev/cloudnative-pg-extension/actions/workflows/security-scan.yml/badge.svg)](https://github.com/dingo4dev/cloudnative-pg-extension/actions/workflows/security-scan.yml) + This project provides a Docker image for PostgreSQL with Oracle Foreign Data Wrapper (FDW) support, enabling seamless interaction between PostgreSQL and Oracle databases. The image is built on top of the CloudNative PostgreSQL image and includes the Oracle Instant Client and the oracle_fdw extension. This setup allows PostgreSQL to efficiently query and manipulate data stored in Oracle databases, facilitating data integration and migration scenarios. -Key features of this Docker image include: -- PostgreSQL 17-bullseye as the base database system -- Oracle Instant Client (version 19.25.0.0.0) for Oracle database connectivity -- oracle_fdw extension for creating foreign tables linked to Oracle -- pg_cron extension for scheduling PostgreSQL jobs -- PostgreSQL Anonymizer for data anonymization -- Optimized for CloudNative PostgreSQL environments +## ✨ Key Features + +- 🐘 **Multiple PostgreSQL versions**: 16, 17, and 18 support +- 🔗 **Oracle Integration**: Oracle Instant Client (19.25.0.0.0) with oracle_fdw extension (pinned version) +- ⏰ **Job Scheduling**: pg_cron extension for scheduling PostgreSQL jobs +- 🔒 **Data Anonymization**: PostgreSQL Anonymizer for data masking +- 🏗️ **Multi-Architecture**: AMD64 and ARM64 support +- 🔍 **Health Checks**: Built-in health check for container orchestration +- ☁️ **Cloud Native**: Optimized for CloudNative PostgreSQL operator in Kubernetes +- 🔐 **Security**: Automated security scanning with Trivy + +## Supported PostgreSQL Versions + +This project supports multiple PostgreSQL versions: +- PostgreSQL 16 (version 16.6) +- PostgreSQL 17 (version 17.1.5) +- PostgreSQL 18 (version 18.4) - **Latest** + +Each version is built with the same Oracle integration capabilities. + +### Available Docker Images + +The images are automatically built and published to both Docker Hub and GitHub Container Registry: + +**Version-specific tags:** +- `dingo4dev/postgres-container:16.6` or `ghcr.io/dingo4dev/postgres-container:16.6` +- `dingo4dev/postgres-container:17.1.5` or `ghcr.io/dingo4dev/postgres-container:17.1.5` +- `dingo4dev/postgres-container:18.4` or `ghcr.io/dingo4dev/postgres-container:18.4` + +**Major version tags:** +- `dingo4dev/postgres-container:16` - Latest PostgreSQL 16.x +- `dingo4dev/postgres-container:17` - Latest PostgreSQL 17.x +- `dingo4dev/postgres-container:18` - Latest PostgreSQL 18.x + +**Latest tag:** +- `dingo4dev/postgres-container:latest` - Always points to the newest PostgreSQL version (18.4) + +**Multi-Architecture:** +All images support both `linux/amd64` and `linux/arm64` architectures. ## Repository Structure -- `Dockerfile`: Contains the instructions for building the Docker image -- `README.md`: This file, providing project documentation +- `Dockerfile`: Multi-version PostgreSQL image with Oracle FDW +- `docker-compose.yml`: Docker Compose setup for local development +- `build-versions.sh`: Helper script to build multiple PostgreSQL versions +- `.github/workflows/`: CI/CD workflows (build, test, security scanning) +- `examples/kubernetes/`: Kubernetes deployment examples with CNPG operator +- `init-scripts/`: Example initialization SQL scripts +- `tutorials/`: Usage tutorials and examples +- `CONTRIBUTING.md`: Contribution guidelines + +## 🚀 Quick Start + +The fastest way to get started is using Docker Compose: + +```bash +# Clone the repository +git clone https://github.com/dingo4dev/cloudnative-pg-extension.git +cd cloudnative-pg-extension + +# Copy environment file +cp .env.example .env + +# Start PostgreSQL with Oracle FDW +docker-compose up -d + +# Check logs +docker-compose logs -f + +# Connect to PostgreSQL +docker-compose exec postgres psql -U postgres -d app +``` + +The extensions (oracle_fdw, pg_cron, anon) will be automatically initialized on first startup. ## Usage Instructions ### Prerequisites -- Docker installed on your system -- Access to the ghcr.io container registry +- Docker 20.10 or later +- Docker Compose (optional, for local development) +- Access to ghcr.io or Docker Hub + +### Using Pre-built Images + +Pull and run a pre-built image: + +```bash +# Pull latest version +docker pull dingo4dev/postgres-container:latest + +# Or pull specific version +docker pull dingo4dev/postgres-container:18.4 + +# Run container +docker run -d \ + --name postgres-oracle \ + -p 5432:5432 \ + -e POSTGRES_PASSWORD=mysecretpassword \ + -e POSTGRES_DB=app \ + dingo4dev/postgres-container:latest +``` + +### Using Docker Compose + +For local development, use the provided `docker-compose.yml`: + +```bash +# Start services +docker-compose up -d + +# View logs +docker-compose logs -f postgres + +# Execute SQL +docker-compose exec postgres psql -U postgres -d app -c "\dx" + +# Stop services +docker-compose down +``` + +Customize by editing `.env` file or `docker-compose.yml`. ### Building the Docker Image -To build the Docker image locally, run the following command in the repository root: +To build the Docker image locally, you can specify the PostgreSQL version using build arguments: + +#### Using the Build Script (Recommended) + +We provide a convenient build script that handles version management: ```bash -docker build -t postgres-oracle-fdw . +# Build all supported versions +./build-versions.sh all + +# Build a specific version +./build-versions.sh 16 # Builds PostgreSQL 16.6 +./build-versions.sh 17 # Builds PostgreSQL 17.1.5 +./build-versions.sh 18 # Builds PostgreSQL 18.4 ``` -### Running the Container +#### Manual Build Commands -To start a container using this image: +Alternatively, you can build manually with Docker: ```bash -docker run -d --name postgres-oracle -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres-oracle-fdw +# Build PostgreSQL 18 (latest) +docker build \ + --build-arg PG_MAJOR=18 \ + --build-arg PG_VERSION=18.4 \ + --build-arg ORACLE_VERSION=19.25.0.0.0 \ + --build-arg ORACLE_FDW_VERSION=ORACLE_FDW_2_8_0 \ + -t postgres-oracle-fdw:18.4 . + +# Build for multiple architectures +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --build-arg PG_MAJOR=18 \ + --build-arg PG_VERSION=18.4 \ + -t postgres-oracle-fdw:18.4 . ``` -Replace `mysecretpassword` with a secure password of your choice. +### Deploying to Kubernetes + +For CloudNative PostgreSQL operator deployments, see the [Kubernetes examples](./examples/kubernetes/README.md). + +```bash +# Apply cluster configuration +kubectl apply -f examples/kubernetes/cluster.yaml + +# Check cluster status +kubectl get cluster postgres-oracle-fdw +``` ### Connecting to the Database @@ -254,8 +393,9 @@ Note: The Oracle Instant Client and oracle_fdw extension act as intermediaries, The project defines the following infrastructure in the Dockerfile: -- Base Image: `ghcr.io/cloudnative-pg/postgresql:17-bullseye` -- Oracle Instant Client: Version 19.25.0.0.0 +- Base Image: `ghcr.io/cloudnative-pg/postgresql:{PG_MAJOR}-bullseye` + - Supports PostgreSQL 17 and 18 (configurable via PG_MAJOR build argument) +- Oracle Instant Client: Version 19.25.0.0.0 (configurable via ORACLE_VERSION build argument) - Purpose: Provides connectivity to Oracle databases - oracle_fdw Extension: - Purpose: Enables creation and use of foreign tables linked to Oracle databases @@ -300,8 +440,30 @@ Thank you for helping improve this project! This section documents the recent changes and updates to the project: -- Initial release of the PostgreSQL Docker image with Oracle FDW support -- Base image: CloudNative PostgreSQL 17-bullseye +### Latest Enhancements (Current) +- ✅ **Multi-Architecture Support**: Added ARM64 and AMD64 builds for all versions +- ✅ **Health Checks**: Implemented container health checks for orchestration +- ✅ **Latest Tag**: Added `:latest` tag pointing to newest PostgreSQL version +- ✅ **PostgreSQL 16 Support**: Added PostgreSQL 16.6 to supported versions +- ✅ **Version Pinning**: Pinned oracle_fdw by PostgreSQL major (PG16/17: ORACLE_FDW_2_7_0, PG18: ORACLE_FDW_2_8_0) +- ✅ **Docker Compose**: Added docker-compose.yml for local development +- ✅ **Automated Testing**: CI/CD now includes extension loading tests +- ✅ **Security Scanning**: Added Trivy security scanning workflow +- ✅ **Kubernetes Examples**: Added CloudNative PostgreSQL operator manifests +- ✅ **GitHub Templates**: Added PR template and issue templates +- ✅ **Contributing Guide**: Added comprehensive CONTRIBUTING.md +- ✅ **Dependabot**: Configured automated dependency updates +- ✅ **Build Improvements**: Enhanced workflow with caching and parallel builds + +### Version 18.4 Support Added +- Added support for PostgreSQL 18.4 +- Implemented matrix build strategy for building multiple PostgreSQL versions +- Updated CI/CD workflow to build PostgreSQL 16, 17, and 18 +- Made Dockerfile version-agnostic with build arguments +- Updated documentation to reflect multi-version support + +### Initial Release +- Base image: CloudNative PostgreSQL bullseye - Included Oracle Instant Client version 19.25.0.0.0 - Added oracle_fdw extension for Oracle database connectivity - Integrated pg_cron extension for job scheduling @@ -310,4 +472,18 @@ This section documents the recent changes and updates to the project: - Changed postgres user UID to 26 for improved container security - Optimized for CloudNative PostgreSQL environments +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. + +- 🐛 Report bugs via [GitHub Issues](https://github.com/dingo4dev/cloudnative-pg-extension/issues) +- 💡 Request features via [GitHub Issues](https://github.com/dingo4dev/cloudnative-pg-extension/issues) +- 🔧 Submit Pull Requests following our PR template +- 📖 Improve documentation +- ⭐ Star the repository if you find it useful! + +## License + +This project is licensed under the GNU General Public License v3.0 - see the [LICENSE.md](LICENSE.md) file for details. + Note: This changelog represents the current state of the project. Future updates will be added to this section as they occur. \ No newline at end of file diff --git a/build-versions.sh b/build-versions.sh new file mode 100755 index 0000000..842eed0 --- /dev/null +++ b/build-versions.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Build script for multiple PostgreSQL versions +# Usage: ./build-versions.sh [version] +# Example: ./build-versions.sh 16 +# ./build-versions.sh 17 +# ./build-versions.sh 18 +# ./build-versions.sh all + +set -e + +ORACLE_VERSION="19.25.0.0.0" + +# Define PostgreSQL versions +declare -A PG_VERSIONS +PG_VERSIONS[16]="16.6" +PG_VERSIONS[17]="17.1.5" +PG_VERSIONS[18]="18.4" + +build_version() { + local major=$1 + local version=${PG_VERSIONS[$major]} + local oracle_fdw_version="ORACLE_FDW_2_7_0" + + if [ "$major" = "18" ]; then + oracle_fdw_version="ORACLE_FDW_2_8_0" + fi + + if [ -z "$version" ]; then + echo "Error: Unknown PostgreSQL version: $major" + echo "Available versions: ${!PG_VERSIONS[@]}" + exit 1 + fi + + echo "Building PostgreSQL $major ($version) with Oracle $ORACLE_VERSION and oracle_fdw $oracle_fdw_version..." + docker build \ + --build-arg PG_MAJOR=$major \ + --build-arg PG_VERSION=$version \ + --build-arg ORACLE_VERSION=$ORACLE_VERSION \ + --build-arg ORACLE_FDW_VERSION=$oracle_fdw_version \ + -t postgres-oracle-fdw:$version \ + -t postgres-oracle-fdw:$major \ + . + + echo "✓ Successfully built postgres-oracle-fdw:$version" +} + +# Main script logic +case "${1:-all}" in + all) + echo "Building all PostgreSQL versions..." + for major in $(echo "${!PG_VERSIONS[@]}" | tr ' ' '\n' | sort -n); do + build_version "$major" + done + echo "✓ All versions built successfully!" + docker images postgres-oracle-fdw + ;; + 16|17|18) + build_version "$1" + ;; + *) + echo "Usage: $0 [version]" + echo " version: 16, 17, 18, or all (default: all)" + echo "Available versions:" + for major in $(echo "${!PG_VERSIONS[@]}" | tr ' ' '\n' | sort -n); do + echo " - PostgreSQL $major: ${PG_VERSIONS[$major]}" + done + exit 1 + ;; +esac diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..39c06a9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.8' + +services: + # PostgreSQL with Oracle FDW + postgres: + build: + context: . + args: + PG_MAJOR: 18 + PG_VERSION: 18.4 + ORACLE_VERSION: 19.25.0.0.0 + ORACLE_FDW_VERSION: ORACLE_FDW_2_8_0 + image: postgres-oracle-fdw:18.4 + container_name: postgres-oracle-fdw + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + POSTGRES_DB: ${POSTGRES_DB:-app} + POSTGRES_USER: ${POSTGRES_USER:-postgres} + ports: + - "${POSTGRES_PORT:-5432}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init-scripts:/docker-entrypoint-initdb.d + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - postgres_network + + # Optional: Oracle Database (for testing FDW connectivity) + # Uncomment if you want to run a local Oracle database for testing + # oracle: + # image: container-registry.oracle.com/database/express:21.3.0-xe + # container_name: oracle-xe + # environment: + # ORACLE_PWD: ${ORACLE_PASSWORD:-OraclePassword123} + # ports: + # - "1521:1521" + # - "5500:5500" + # volumes: + # - oracle_data:/opt/oracle/oradata + # networks: + # - postgres_network + # healthcheck: + # test: ["CMD-SHELL", "echo 'SELECT 1 FROM DUAL;' | sqlplus -s system/${ORACLE_PASSWORD:-OraclePassword123}@localhost:1521/XE"] + # interval: 30s + # timeout: 10s + # retries: 5 + # start_period: 60s + +volumes: + postgres_data: + driver: local + # oracle_data: + # driver: local + +networks: + postgres_network: + driver: bridge diff --git a/examples/kubernetes/README.md b/examples/kubernetes/README.md new file mode 100644 index 0000000..2e1e517 --- /dev/null +++ b/examples/kubernetes/README.md @@ -0,0 +1,156 @@ +# CloudNative PostgreSQL with Oracle FDW - Kubernetes Examples + +This directory contains example Kubernetes manifests for deploying PostgreSQL with Oracle FDW support using the CloudNative PostgreSQL operator. + +## Prerequisites + +1. **Install CloudNative PostgreSQL Operator** + ```bash + kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.22/releases/cnpg-1.22.0.yaml + ``` + +2. **Verify operator is running** + ```bash + kubectl get pods -n cnpg-system + ``` + +## Deployment + +### 1. Deploy PostgreSQL Cluster + +Deploy a 3-instance PostgreSQL cluster with Oracle FDW: + +```bash +kubectl apply -f cluster.yaml +``` + +Check cluster status: +```bash +kubectl get cluster postgres-oracle-fdw +kubectl get pods -l cnpg.io/cluster=postgres-oracle-fdw +``` + +### 2. Configure Oracle FDW Connection (Optional) + +If you have an Oracle database to connect to: + +1. Edit `oracle-fdw-config.yaml` and update the Oracle connection details +2. Apply the configuration: + ```bash + kubectl apply -f oracle-fdw-config.yaml + ``` + +### 3. Connect to PostgreSQL + +Get the connection details: +```bash +# Get the service +kubectl get svc -l cnpg.io/cluster=postgres-oracle-fdw + +# Get the app user password +kubectl get secret postgres-oracle-fdw-app -o jsonpath='{.data.password}' | base64 -d +``` + +Connect using psql: +```bash +kubectl run -it --rm psql --image=postgres:16 --restart=Never -- \ + psql -h postgres-oracle-fdw-rw -U app -d app +``` + +## Files Description + +- **cluster.yaml**: Main cluster definition with 3 replicas +- **oracle-fdw-config.yaml**: Oracle FDW server and foreign table setup + +## Cluster Features + +The deployed cluster includes: +- ✅ 3 PostgreSQL instances (1 primary, 2 replicas) +- ✅ oracle_fdw extension for Oracle connectivity +- ✅ pg_cron extension for job scheduling +- ✅ PostgreSQL Anonymizer for data masking +- ✅ High availability with automatic failover +- ✅ Connection pooling +- ✅ Pod monitoring enabled + +## Customization + +### Change PostgreSQL Version + +Edit `cluster.yaml` and change the `imageName`: +```yaml +imageName: ghcr.io/dingo4dev/postgres-container:17.1.5 # For PG 17 +imageName: ghcr.io/dingo4dev/postgres-container:16.6 # For PG 16 +``` + +### Adjust Resources + +Modify the `resources` section in `cluster.yaml`: +```yaml +resources: + requests: + memory: "1Gi" + cpu: "1000m" + limits: + memory: "2Gi" + cpu: "2000m" +``` + +### Configure Backups + +Uncomment the backup section in `cluster.yaml` and configure your S3-compatible storage. + +## Monitoring + +The cluster has `enablePodMonitor: true`, which allows Prometheus to scrape metrics. + +View metrics: +```bash +kubectl port-forward svc/postgres-oracle-fdw-rw 9187:9187 +``` + +## Testing Oracle FDW + +Once connected to the database: + +```sql +-- Check extensions +\dx + +-- List foreign servers +\des+ + +-- List foreign tables +\dE+ + +-- Query Oracle data +SELECT * FROM oracle_employees LIMIT 10; +``` + +## Cleanup + +Remove all resources: +```bash +kubectl delete -f oracle-fdw-config.yaml +kubectl delete -f cluster.yaml +``` + +## Troubleshooting + +**Cluster not starting:** +```bash +kubectl describe cluster postgres-oracle-fdw +kubectl logs -l cnpg.io/cluster=postgres-oracle-fdw -c postgres +``` + +**Extension issues:** +```bash +kubectl exec -it postgres-oracle-fdw-1 -- psql -U postgres -c "\dx" +``` + +**Oracle connectivity:** +```bash +# Check from pod +kubectl exec -it postgres-oracle-fdw-1 -- bash +tnsping oracle-host:1521 +``` diff --git a/examples/kubernetes/cluster.yaml b/examples/kubernetes/cluster.yaml new file mode 100644 index 0000000..fc2d256 --- /dev/null +++ b/examples/kubernetes/cluster.yaml @@ -0,0 +1,55 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: postgres-oracle-fdw + namespace: default +spec: + instances: 3 + imageName: ghcr.io/dingo4dev/postgres-container:18.4 + + postgresql: + parameters: + shared_preload_libraries: "oracle_fdw,pg_cron" + cron.database_name: "app" + max_connections: "100" + shared_buffers: "256MB" + + bootstrap: + initdb: + database: app + owner: app + postInitSQL: + - CREATE EXTENSION IF NOT EXISTS oracle_fdw; + - CREATE EXTENSION IF NOT EXISTS pg_cron; + - CREATE EXTENSION IF NOT EXISTS anon; + - ALTER DATABASE app SET anon.transparent_dynamic_masking TO true; + + storage: + size: 10Gi + storageClass: standard + + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + + # Backup configuration (optional) + # backup: + # barmanObjectStore: + # destinationPath: s3://my-backup-bucket/postgres-oracle-fdw + # s3Credentials: + # accessKeyId: + # name: aws-credentials + # key: ACCESS_KEY_ID + # secretAccessKey: + # name: aws-credentials + # key: SECRET_ACCESS_KEY + # wal: + # compression: gzip + # retentionPolicy: "30d" + + monitoring: + enablePodMonitor: true diff --git a/examples/kubernetes/oracle-fdw-config.yaml b/examples/kubernetes/oracle-fdw-config.yaml new file mode 100644 index 0000000..ae8fb70 --- /dev/null +++ b/examples/kubernetes/oracle-fdw-config.yaml @@ -0,0 +1,79 @@ +apiVersion: v1 +kind: Secret +metadata: + name: oracle-fdw-connection + namespace: default +type: Opaque +stringData: + server: "//oracle-host:1521/ORCLPDB1" + username: "oracle_user" + password: "oracle_password" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: oracle-fdw-setup + namespace: default +data: + setup.sql: | + -- Create Oracle server using credentials from secret + CREATE SERVER IF NOT EXISTS oracle_server + FOREIGN DATA WRAPPER oracle_fdw + OPTIONS (dbserver '//oracle-host:1521/ORCLPDB1'); + + -- Create user mapping + CREATE USER MAPPING IF NOT EXISTS FOR app + SERVER oracle_server + OPTIONS (user 'oracle_user', password 'oracle_password'); + + -- Example foreign table + CREATE FOREIGN TABLE IF NOT EXISTS oracle_employees ( + employee_id integer, + first_name text, + last_name text, + email text, + hire_date date + ) + SERVER oracle_server + OPTIONS (schema 'HR', table 'EMPLOYEES'); + + -- Grant access to app user + GRANT SELECT ON oracle_employees TO app; +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: setup-oracle-fdw + namespace: default +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: setup + image: ghcr.io/dingo4dev/postgres-container:18.4 + command: + - /bin/bash + - -c + - | + # Wait for PostgreSQL cluster to be ready + until pg_isready -h postgres-oracle-fdw-rw -U app; do + echo "Waiting for PostgreSQL..." + sleep 5 + done + + # Execute setup SQL + PGPASSWORD="${POSTGRES_PASSWORD}" psql -h postgres-oracle-fdw-rw -U app -d app -f /scripts/setup.sql + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-oracle-fdw-app + key: password + volumeMounts: + - name: setup-scripts + mountPath: /scripts + volumes: + - name: setup-scripts + configMap: + name: oracle-fdw-setup diff --git a/init-scripts/01-init-extensions.sql b/init-scripts/01-init-extensions.sql new file mode 100644 index 0000000..d734d9e --- /dev/null +++ b/init-scripts/01-init-extensions.sql @@ -0,0 +1,43 @@ +-- Example initialization script for PostgreSQL with Oracle FDW +-- This script runs automatically when the container starts for the first time + +-- Create extensions +CREATE EXTENSION IF NOT EXISTS oracle_fdw; +CREATE EXTENSION IF NOT EXISTS pg_cron; +CREATE EXTENSION IF NOT EXISTS anon; + +-- Initialize pg_cron with app database +-- Note: pg_cron is already configured in postgresql.conf to use 'app' database + +-- Example: Create Oracle server (update with your Oracle connection details) +-- Uncomment and modify the following lines: + +-- CREATE SERVER oracle_server +-- FOREIGN DATA WRAPPER oracle_fdw +-- OPTIONS (dbserver '//oracle-xe:1521/XE'); + +-- CREATE USER MAPPING FOR postgres +-- SERVER oracle_server +-- OPTIONS (user 'system', password 'OraclePassword123'); + +-- Example: Create a foreign table +-- CREATE FOREIGN TABLE oracle_example ( +-- id integer, +-- name text +-- ) +-- SERVER oracle_server +-- OPTIONS (schema 'SYSTEM', table 'EXAMPLE_TABLE'); + +-- Initialize anonymizer +ALTER DATABASE app SET anon.transparent_dynamic_masking TO true; + +-- Example: Create an anonymous role +CREATE ROLE anonymous LOGIN PASSWORD 'anonymous_pass'; +SECURITY LABEL FOR anon ON ROLE anonymous IS 'MASKED'; +GRANT pg_read_all_data TO anonymous; + +-- Log completion +DO $$ +BEGIN + RAISE NOTICE 'Extensions initialized: oracle_fdw, pg_cron, anon'; +END $$;