diff --git a/Makefile b/Makefile
index e9848cb8..4a3ddf5c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
.PHONY: all
all: install test build-image
-TAG=$(shell mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+TAG?=$(shell mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
COSIGN_PASSWORD := $(COSIGN_PASSWORD)
@@ -158,14 +158,14 @@ verify-image:
.PHONY: build-acceptance
build-acceptance:
- docker build --tag=acceptance:$(TAG) acceptance_tests
+ docker build --tag=geoservercloud/acceptance:latest acceptance_tests
.PHONY: acceptance-tests-datadir
acceptance-tests-datadir: build-acceptance start-acceptance-tests-datadir run-acceptance-tests-datadir
.PHONY: start-acceptance-tests-datadir
start-acceptance-tests-datadir:
- (cd compose/ && ./acceptance_datadir up -d)
+ (cd compose/ && TAG=$(TAG) ./acceptance_datadir up -d)
.PHONY: run-acceptance-tests-datadir
run-acceptance-tests-datadir:
@@ -173,14 +173,14 @@ run-acceptance-tests-datadir:
.PHONY: clean-acceptance-tests-datadir
clean-acceptance-tests-datadir:
- (cd compose/ && ./acceptance_datadir down -v)
+ (cd compose/ && TAG=$(TAG) ./acceptance_datadir down -v)
.PHONY: acceptance-tests-pgconfig
acceptance-tests-pgconfig: build-acceptance start-acceptance-tests-pgconfig run-acceptance-tests-pgconfig
.PHONY: start-acceptance-tests-pgconfig
start-acceptance-tests-pgconfig:
- (cd compose/ && ./acceptance_pgconfig up -d)
+ (cd compose/ && TAG=$(TAG) ./acceptance_pgconfig up -d)
.PHONY: run-acceptance-tests-pgconfig
run-acceptance-tests-pgconfig:
@@ -188,7 +188,7 @@ run-acceptance-tests-pgconfig:
.PHONY: clean-acceptance-tests-pgconfig
clean-acceptance-tests-pgconfig:
- (cd compose/ && ./acceptance_pgconfig down -v)
+ (cd compose/ && TAG=$(TAG) ./acceptance_pgconfig down -v)
.PHONY: acceptance-tests-jdbcconfig
acceptance-tests-jdbcconfig: build-acceptance start-acceptance-tests-jdbcconfig run-acceptance-tests-jdbcconfig
diff --git a/acceptance_tests/README.md b/acceptance_tests/README.md
index 50ba5b58..6472dbf1 100644
--- a/acceptance_tests/README.md
+++ b/acceptance_tests/README.md
@@ -2,17 +2,190 @@
## Requirements
-[Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)
+- Python 3.8+
+- [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) (recommended)
+- Or a Python virtual environment
## Installation
+### Option 1: Using Poetry (recommended)
+
```shell
poetry install
```
-# Run the tests
-First start the docker composition then run:
+### Option 2: Using Python virtual environment
```shell
-GEOSERVER_URL=http://localhost:9090/geoserver/cloud poetry run pytest -vvv .
-```
\ No newline at end of file
+# Create virtual environment
+python -m venv .venv
+
+# Activate virtual environment
+# On Linux/macOS:
+source .venv/bin/activate
+# On Windows:
+.venv\Scripts\activate
+
+# Install dependencies
+pip install -e .
+```
+
+## Running the tests
+
+### Option 1: Using make (runs full docker composition)
+
+```shell
+# Run tests with datadir backend
+make acceptance-tests-datadir
+
+# Run tests with pgconfig backend
+make acceptance-tests-pgconfig
+```
+
+### Option 2: Manual execution
+
+#### Run tests inside Docker container (recommended for all tests)
+
+```shell
+# Start GeoServer services
+cd ../compose
+./acceptance_datadir up -d # or ./acceptance_pgconfig up -d
+
+# Optional: Start webui service if needed (not started by default in acceptance composition)
+./acceptance_datadir scale webui=1
+
+# Run all tests inside the container
+./acceptance_datadir exec acceptance pytest . -vvv --color=yes
+
+# Run specific tests inside the container
+./acceptance_datadir exec acceptance pytest tests/test_cog.py -v --color=yes
+```
+
+#### Run tests from host machine (full functionality)
+
+**Note:** This requires the geodatabase port to be exposed (port 5433). The acceptance composition now exposes this port automatically.
+
+```shell
+# Start GeoServer services (geodatabase port 5433 will be exposed)
+cd ../compose
+./acceptance_datadir up -d # or ./acceptance_pgconfig up -d
+
+# Optional: Start webui service if needed
+./acceptance_datadir scale webui=1
+
+# From acceptance_tests directory, run tests from host
+cd ../acceptance_tests
+./run_tests_locally.sh tests/test_cog.py # Run COG tests
+./run_tests_locally.sh tests/test_imagemosaic_cog.py # Run ImageMosaic tests
+./run_tests_locally.sh tests/test_workspace.py # Run workspace tests
+./run_tests_locally.sh # Run all tests
+
+# Run specific test functions
+./run_tests_locally.sh tests/test_imagemosaic_cog.py::test_create_imagemosaic_local_files
+./run_tests_locally.sh tests/test_cog.py::test_create_cog_coverage
+```
+
+### Run specific tests with make
+
+```shell
+# To run specific tests with make, you can modify the Makefile or use the manual Docker approach above
+```
+
+## Debugging
+
+If you need to debug the GeoServer services, you can run the acceptance test composition with local ports exposed:
+
+```shell
+cd ../compose
+
+# Start the acceptance test compose with local ports
+./acceptance_datadir -f localports.yml up -d
+
+# Enable the webui service if needed
+./acceptance_datadir -f localports.yml scale webui=1
+
+# Shut down the rest service if you're going to launch it from your IDE
+./acceptance_datadir -f localports.yml down rest
+
+# Now you can run from the IDE with the `local` spring profile enabled
+# and the required catalog backend profile (datadir/pgconfig)
+```
+
+### Accessing Sample Data
+
+When debugging, you may need to access the sample data that's available in the containers. The sample data is extracted to `/mnt/geoserver_data/sampledata` inside the containers. To access it from your local development environment:
+
+```shell
+# Check what sample data is available
+./acceptance_datadir exec wms find /mnt/geoserver_data/sampledata
+
+# Copy sample data to your local machine for testing
+docker cp $(./acceptance_datadir ps -q wms | head -1):/mnt/geoserver_data/sampledata ./local_sampledata
+
+# Or mount the geoserver_data volume directly to a local directory
+# Add this to your docker-compose override file:
+# volumes:
+# geoserver_data:
+# driver: local
+# driver_opts:
+# type: none
+# o: bind
+# device: /path/to/local/sampledata
+```
+
+## Testing Different GeoServer Cloud Versions
+
+You can test different versions of GeoServer Cloud without modifying the `.env` file by setting the TAG environment variable.
+
+### Option 1: Using Make Commands (Recommended)
+
+```shell
+# Test with GeoServer Cloud 2.27.2-SNAPSHOT (datadir backend)
+TAG=2.27.2-SNAPSHOT make start-acceptance-tests-datadir
+cd acceptance_tests && ./run_tests_locally.sh tests/test_imagemosaic.py
+TAG=2.27.2-SNAPSHOT make clean-acceptance-tests-datadir
+
+# Test with GeoServer Cloud 2.26.2.0 (pgconfig backend)
+TAG=2.26.2.0 make start-acceptance-tests-pgconfig
+cd acceptance_tests && ./run_tests_locally.sh tests/test_imagemosaic.py
+TAG=2.26.2.0 make clean-acceptance-tests-pgconfig
+
+# Test with default version (from Maven project.version)
+make start-acceptance-tests-datadir
+cd acceptance_tests && ./run_tests_locally.sh
+make clean-acceptance-tests-datadir
+```
+
+### Option 2: Manual Docker Compose Commands
+
+```shell
+# Test with GeoServer Cloud 2.27.1.0
+cd ../compose
+TAG=2.27.1.0 ./acceptance_datadir up -d
+
+# Run your tests
+cd ../acceptance_tests
+./run_tests_locally.sh
+
+# Test with GeoServer Cloud 2.26.2.0
+cd ../compose
+./acceptance_datadir down
+TAG=2.26.2.0 ./acceptance_datadir up -d
+
+# Run your tests again
+cd ../acceptance_tests
+./run_tests_locally.sh
+
+# Return to default version (check .env file for current default)
+cd ../compose
+./acceptance_datadir down
+./acceptance_datadir up -d
+```
+
+## Cleanup
+
+```shell
+# Stop and remove containers
+cd ../compose
+./acceptance_datadir down -v # or ./acceptance_pgconfig down -v
+```
diff --git a/acceptance_tests/run_tests_locally.sh b/acceptance_tests/run_tests_locally.sh
new file mode 100755
index 00000000..9dbc49c5
--- /dev/null
+++ b/acceptance_tests/run_tests_locally.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+
+# Default GeoServer URL
+GEOSERVER_URL=${GEOSERVER_URL:-"http://localhost:9090/geoserver/cloud"}
+
+# Default database connection for local testing (requires geodatabase port exposed on 5433)
+PGHOST=${PGHOST:-"localhost"}
+PGPORT=${PGPORT:-"5433"}
+PGDATABASE=${PGDATABASE:-"geodata"}
+PGUSER=${PGUSER:-"geodata"}
+PGPASSWORD=${PGPASSWORD:-"geodata"}
+PGSCHEMA=${PGSCHEMA:-"test1"}
+
+# Note: This script runs tests from the host machine and requires the geodatabase
+# port to be exposed (5433). Start services with: ./acceptance_datadir up -d
+
+# Help function
+show_help() {
+ echo "Usage: $0 [OPTIONS] [TEST_PATH]"
+ echo ""
+ echo "Run GeoServer Cloud acceptance tests locally"
+ echo ""
+ echo "OPTIONS:"
+ echo " -h, --help Show this help message"
+ echo " -v, --verbose Run with verbose output (-vvv)"
+ echo " -q, --quiet Run with minimal output (-q)"
+ echo ""
+ echo "TEST_PATH:"
+ echo " Optional path to specific test file or test function"
+ echo " Examples:"
+ echo " $0 tests/test_cog.py"
+ echo " $0 tests/test_cog_imagemosaic.py::test_create_imagemosaic_cogs_http"
+ echo " $0 tests/test_workspace.py"
+ echo ""
+ echo "Environment variables:"
+ echo " GEOSERVER_URL GeoServer URL (default: http://localhost:9090/geoserver/cloud)"
+ echo " PGHOST Database host (default: localhost)"
+ echo " PGPORT Database port (default: 5433)"
+ echo ""
+ echo "Examples:"
+ echo " $0 # Run all tests"
+ echo " $0 tests/test_cog.py # Run COG tests only"
+ echo " $0 -v # Run all tests with verbose output"
+ echo " GEOSERVER_URL=http://localhost:8080/geoserver $0 # Use different URL"
+}
+
+# Parse command line arguments
+VERBOSE_FLAG="-v"
+TEST_PATH=""
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -h|--help)
+ show_help
+ exit 0
+ ;;
+ -v|--verbose)
+ VERBOSE_FLAG="-vvv"
+ shift
+ ;;
+ -q|--quiet)
+ VERBOSE_FLAG="-q"
+ shift
+ ;;
+ -*)
+ echo "Unknown option: $1" >&2
+ show_help >&2
+ exit 1
+ ;;
+ *)
+ TEST_PATH="$1"
+ shift
+ ;;
+ esac
+done
+
+# Set the target (all tests or specific test)
+if [[ -n "$TEST_PATH" ]]; then
+ TARGET="$TEST_PATH"
+else
+ TARGET="."
+fi
+
+echo "Running GeoServer Cloud acceptance tests..."
+echo "GeoServer URL: $GEOSERVER_URL"
+echo "Test target: $TARGET"
+echo "Verbosity: $VERBOSE_FLAG"
+echo ""
+
+# Wait for GeoServer to be available
+echo "Waiting for GeoServer to be available at $GEOSERVER_URL/rest/workspaces..."
+max_attempts=60
+attempt=0
+while [ $attempt -lt $max_attempts ]; do
+ if curl -s -u admin:geoserver --fail "$GEOSERVER_URL/rest/workspaces" > /dev/null 2>&1; then
+ echo "✓ GeoServer is ready!"
+ break
+ fi
+ attempt=$((attempt + 1))
+ echo " Attempt $attempt/$max_attempts - waiting 5 seconds..."
+ sleep 5
+done
+
+if [ $attempt -eq $max_attempts ]; then
+ echo "✗ Timeout: GeoServer did not become available at $GEOSERVER_URL/rest/workspaces"
+ echo " Please ensure the services are running with: cd ../compose && ./acceptance_datadir up -d"
+ exit 1
+fi
+
+echo ""
+
+# Check if we're using Poetry or virtual environment
+if command -v poetry &> /dev/null && [[ -f "pyproject.toml" ]]; then
+ echo "Using Poetry to run tests..."
+ echo "Installing dependencies if needed..."
+ poetry install
+ export GEOSERVER_URL PGHOST PGPORT PGDATABASE PGUSER PGPASSWORD PGSCHEMA
+ poetry run pytest $VERBOSE_FLAG --color=yes $TARGET
+elif [[ -n "$VIRTUAL_ENV" ]]; then
+ echo "Using activated virtual environment..."
+ # Check if pytest is available
+ if ! command -v pytest &> /dev/null; then
+ echo "Error: pytest not found in virtual environment"
+ echo "Please install dependencies: pip install -e ."
+ exit 1
+ fi
+ export GEOSERVER_URL PGHOST PGPORT PGDATABASE PGUSER PGPASSWORD PGSCHEMA
+ pytest $VERBOSE_FLAG --color=yes $TARGET
+else
+ echo "Error: Please either:"
+ echo " 1. Install Poetry (https://python-poetry.org/docs/#installing-with-the-official-installer) and run this script again, or"
+ echo " 2. Create and activate a virtual environment:"
+ echo " python -m venv .venv"
+ echo " source .venv/bin/activate # Linux/macOS"
+ echo " pip install -e ."
+ echo " ./run_tests_locally.sh"
+ echo ""
+ exit 1
+fi
diff --git a/acceptance_tests/tests/conftest.py b/acceptance_tests/tests/conftest.py
index e450c66c..7bcd8427 100644
--- a/acceptance_tests/tests/conftest.py
+++ b/acceptance_tests/tests/conftest.py
@@ -7,13 +7,13 @@ from geoservercloud import GeoServerCloud
GEOSERVER_URL = os.getenv("GEOSERVER_URL", "http://gateway:8080/geoserver/cloud")
RESOURCE_DIR = Path(__file__).parent / "resources"
-# Database connection
-PGHOST = "geodatabase"
-PGPORT = 5432
-PGDATABASE = "geodata"
-PGUSER = "geodata"
-PGPASSWORD = "geodata"
-PGSCHEMA = "test1"
+# Database connection - defaults for container, can be overridden for local testing
+PGHOST = os.getenv("PGHOST", "geodatabase")
+PGPORT = int(os.getenv("PGPORT", "5432"))
+PGDATABASE = os.getenv("PGDATABASE", "geodata")
+PGUSER = os.getenv("PGUSER", "geodata")
+PGPASSWORD = os.getenv("PGPASSWORD", "geodata")
+PGSCHEMA = os.getenv("PGSCHEMA", "test1")
WORKSPACE = "test_workspace"
DATASTORE = "test_datastore"
diff --git a/acceptance_tests/tests/test_cog.py b/acceptance_tests/tests/test_cog.py
new file mode 100644
index 00000000..f75c6fce
--- /dev/null
+++ b/acceptance_tests/tests/test_cog.py
@@ -0,0 +1,78 @@
+import pytest
+from geoservercloud import GeoServerCloud
+from conftest import GEOSERVER_URL
+
+
+def test_create_cog_coverage():
+ """Test creating a COG coverage store and coverage"""
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "cog"
+ store_name = "land_shallow_topo_21600_NW_cog"
+ coverage_name = "land_shallow_topo_NW"
+
+ # Delete and recreate workspace
+ geoserver.delete_workspace(workspace)
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201
+
+ # Create COG coverage store
+ store_xml = f"""
+ {store_name}
+ GeoTIFF
+ true
+ {workspace}
+ cog://https://test-data-cog-public.s3.amazonaws.com/public/land_shallow_topo_21600_NW_cog.tif
+
+
+
+ HTTP
+
+
+
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores",
+ data=store_xml,
+ headers={"Content-Type": "application/xml"}
+ )
+ assert response.status_code == 201
+
+ # Create coverage
+ coverage_xml = f"""
+ {coverage_name}
+ {store_name}
+ """
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "application/xml"}
+ )
+ assert response.status_code == 201
+
+ # Verify the coverage was created - try listing coverages first
+ list_response = geoserver.get_request(f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.json")
+ if list_response.status_code != 200:
+ print(f"Coverage listing failed: {list_response.status_code} - {list_response.text}")
+ assert list_response.status_code == 200
+
+ # Check specific coverage
+ response = geoserver.get_request(f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages/{coverage_name}.json")
+ assert response.status_code == 200
+
+ # Verify coverage properties
+ coverage_data = response.json()["coverage"]
+ assert coverage_data["name"] == coverage_name
+ assert coverage_data["nativeName"] == coverage_name
+ assert coverage_data["enabled"] == True
+
+ # Test WMS GetMap request
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/jpeg&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200
+ assert wms_response.headers.get("content-type").startswith("image/jpeg")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
diff --git a/acceptance_tests/tests/test_imagemosaic.py b/acceptance_tests/tests/test_imagemosaic.py
new file mode 100644
index 00000000..bea19473
--- /dev/null
+++ b/acceptance_tests/tests/test_imagemosaic.py
@@ -0,0 +1,539 @@
+"""
+ImageMosaic acceptance tests for GeoServer Cloud
+
+Tests various workflows for creating ImageMosaic stores and layers:
+- Direct directory creation (like web UI)
+- Manual granule addition
+- Empty store creation with directory/file harvesting
+- XML-based store creation
+
+All tests use sample data from a shared mount volume at /mnt/geoserver_data
+that is accessible to both the test environment and GeoServer containers.
+"""
+import os
+import tempfile
+import zipfile
+from pathlib import Path
+import pytest
+from geoservercloud import GeoServerCloud
+from conftest import GEOSERVER_URL
+
+
+def test_create_imagemosaic_local_files():
+ """Test creating an ImageMosaic using local sample data files via direct directory approach"""
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "local_sampledata"
+ store_name = "ne_pyramid_store"
+
+ # Delete and recreate workspace
+ geoserver.delete_workspace(workspace)
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201
+
+ # Use direct directory approach (like web UI) instead of individual file URLs
+ directory_path = "/mnt/geoserver_data/sampledata/ne/pyramid/"
+
+ # Create ImageMosaic store directly from directory
+ response = geoserver.put_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/external.imagemosaic",
+ data=directory_path,
+ headers={"Content-Type": "text/plain"}
+ )
+ assert response.status_code in [201, 202], f"Failed to create ImageMosaic from directory: {response.text}"
+
+ # List available coverages (should be auto-discovered)
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200, f"Failed to list coverages: {response.text}"
+
+ # Extract the auto-discovered coverage name
+ response_text = response.text
+ import re
+ coverage_match = re.search(r'([^<]+)', response_text)
+ assert coverage_match, f"No coverage found in response: {response_text}"
+
+ coverage_name = coverage_match.group(1)
+
+ # Check if coverage was auto-created (likely scenario)
+ coverage_response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages/{coverage_name}.json"
+ )
+
+ if coverage_response.status_code == 200:
+ # Coverage was auto-created - this is the normal case
+ coverage_data = coverage_response.json()["coverage"]
+ assert coverage_data["name"] == coverage_name
+ assert coverage_data["nativeName"] == coverage_name
+ assert coverage_data["enabled"] == True
+ else:
+ # Coverage not auto-created, create it manually
+ coverage_xml = f"""
+ {coverage_name}
+ Natural Earth Pyramid Mosaic
+ {coverage_name}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Failed to create coverage: {response.text}"
+
+ # Verify the coverage was created
+ response = geoserver.get_request(f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages/{coverage_name}.json")
+ assert response.status_code == 200
+
+ coverage_data = response.json()["coverage"]
+ assert coverage_data["name"] == coverage_name
+ assert coverage_data["nativeName"] == coverage_name
+ assert coverage_data["enabled"] == True
+
+ # Test WMS GetMap request (verify local file mosaic works)
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200, f"WMS GetMap failed: {wms_response.text}"
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
+
+
+def test_create_imagemosaic_manual_granules():
+ """Test creating an ImageMosaic by manually adding individual granules"""
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "manual_granules"
+ store_name = "manual_granules_store"
+ coverage_name = "manual_granules_coverage"
+
+ # Delete and recreate workspace
+ geoserver.delete_workspace(workspace)
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201
+
+ # Create temporary directory for mosaic configuration
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_path = Path(tmp_dir)
+
+ # Create indexer.properties for manual granule addition
+ indexer_content = f"""MosaicCRS=EPSG\\:4326
+Name={coverage_name}
+PropertyCollectors=CRSExtractorSPI(crs),ResolutionExtractorSPI(resolution)
+Schema=*the_geom:Polygon,location:String,crs:String,resolution:String
+CanBeEmpty=true
+AbsolutePath=true"""
+
+ indexer_file = tmp_path / "indexer.properties"
+ indexer_file.write_text(indexer_content)
+
+ # Create datastore.properties (using JNDI like in COG tests)
+ datastore_content = """SPI=org.geotools.data.postgis.PostgisNGJNDIDataStoreFactory
+# JNDI data source
+jndiReferenceName=java:comp/env/jdbc/postgis
+
+#Boolean
+# perform only primary filter on bbox
+# Default Boolean.TRUE
+Loose\\ bbox=true
+
+#Boolean
+# use prepared statements
+#Default Boolean.FALSE
+preparedStatements=false
+"""
+ datastore_file = tmp_path / "datastore.properties"
+ datastore_file.write_text(datastore_content)
+
+ # Create ZIP file with both configuration files
+ zip_file = tmp_path / "manual-granules-config.zip"
+ with zipfile.ZipFile(zip_file, 'w') as zf:
+ zf.write(indexer_file, "indexer.properties")
+ zf.write(datastore_file, "datastore.properties")
+
+ # Create empty ImageMosaic store
+ with open(zip_file, 'rb') as f:
+ zip_data = f.read()
+
+ response = geoserver.put_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/file.imagemosaic?configure=none",
+ data=zip_data,
+ headers={"Content-Type": "application/zip"}
+ )
+ assert response.status_code == 201, f"Failed to create ImageMosaic store: {response.text}"
+
+ # Manually add individual granules from the sample data
+ granule_paths = [
+ "/mnt/geoserver_data/sampledata/ne/pyramid/NE1_LR_LC_SR_W_DR_1_1.tif",
+ "/mnt/geoserver_data/sampledata/ne/pyramid/NE1_LR_LC_SR_W_DR_1_2.tif",
+ "/mnt/geoserver_data/sampledata/ne/pyramid/NE1_LR_LC_SR_W_DR_2_1.tif",
+ "/mnt/geoserver_data/sampledata/ne/pyramid/NE1_LR_LC_SR_W_DR_2_2.tif"
+ ]
+
+ for granule_path in granule_paths:
+ # Use direct file paths (without file:// protocol) for external.imagemosaic
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/external.imagemosaic",
+ data=granule_path,
+ headers={"Content-Type": "text/plain"}
+ )
+ assert response.status_code in [201, 202], f"Failed to add granule {granule_path}: {response.text}"
+
+ # Initialize the store (list available coverages)
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200, f"Failed to list coverages: {response.text}"
+
+ # Verify coverage name is available
+ response_text = response.text
+ assert f"{coverage_name}" in response_text, \
+ f"Coverage name '{coverage_name}' not found in response: {response_text}"
+
+ # Create layer/coverage
+ coverage_xml = f"""
+ {coverage_name}
+ Manual Granules Test Coverage
+ {coverage_name}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Failed to create coverage: {response.text}"
+
+ # Verify the coverage was created successfully
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages/{coverage_name}.json"
+ )
+ assert response.status_code == 200, f"Failed to get coverage details: {response.text}"
+
+ coverage_data = response.json()["coverage"]
+ assert coverage_data["name"] == coverage_name
+ assert coverage_data["nativeName"] == coverage_name
+ assert coverage_data["enabled"] == True
+
+ # Test WMS GetMap request (verify manual granule addition works)
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200, f"WMS GetMap failed: {wms_response.text}"
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
+
+
+def test_create_imagemosaic_empty_store_with_directory_harvest():
+ """
+ Test creating an empty ImageMosaic store first, then harvesting granules from a directory.
+ This tests the workflow: create store -> harvest directory -> create layer.
+ """
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "directory_harvest"
+ store_name = "directory_harvest_store"
+ coverage_name = "directory_harvest_coverage"
+
+ # Clean up any existing workspace
+ geoserver.delete_workspace(workspace)
+
+ # Step 1: Create workspace
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201, f"Failed to create workspace: {response.text}"
+
+ # Step 2: Create ImageMosaic store with configuration
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_path = Path(tmp_dir)
+
+ # Create indexer.properties
+ indexer_content = f"""MosaicCRS=EPSG\\:4326
+Name={coverage_name}
+PropertyCollectors=CRSExtractorSPI(crs),ResolutionExtractorSPI(resolution)
+Schema=*the_geom:Polygon,location:String,crs:String,resolution:String
+CanBeEmpty=true
+AbsolutePath=true"""
+
+ indexer_file = tmp_path / "indexer.properties"
+ indexer_file.write_text(indexer_content)
+
+ # Create datastore.properties (using JNDI)
+ datastore_content = """SPI=org.geotools.data.postgis.PostgisNGJNDIDataStoreFactory
+# JNDI data source
+jndiReferenceName=java:comp/env/jdbc/postgis
+
+#Boolean
+# perform only primary filter on bbox
+# Default Boolean.TRUE
+Loose\\ bbox=true
+
+#Boolean
+# use prepared statements
+#Default Boolean.FALSE
+preparedStatements=false
+"""
+ datastore_file = tmp_path / "datastore.properties"
+ datastore_file.write_text(datastore_content)
+
+ # Create ZIP file with both configuration files
+ zip_file = tmp_path / "mosaic-config.zip"
+ with zipfile.ZipFile(zip_file, 'w') as zf:
+ zf.write(indexer_file, "indexer.properties")
+ zf.write(datastore_file, "datastore.properties")
+
+ # Upload ZIP to create empty ImageMosaic store
+ with open(zip_file, 'rb') as f:
+ zip_data = f.read()
+
+ response = geoserver.put_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/file.imagemosaic?configure=none",
+ data=zip_data,
+ headers={"Content-Type": "application/zip"}
+ )
+ assert response.status_code == 201, f"Failed to create ImageMosaic store: {response.text}"
+
+ # Step 3: Harvest granules from directory
+ harvest_path = "/mnt/geoserver_data/sampledata/ne/pyramid/"
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/external.imagemosaic",
+ data=harvest_path,
+ headers={"Content-Type": "text/plain"}
+ )
+ assert response.status_code in [201, 202], f"Failed to harvest directory {harvest_path}: {response.text}"
+
+ # Step 4: List available coverages
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200, f"Failed to list coverages: {response.text}"
+
+ # Verify coverage name is available
+ response_text = response.text
+ assert f"{coverage_name}" in response_text, \
+ f"Coverage name '{coverage_name}' not found in response: {response_text}"
+
+ # Step 5: Create layer/coverage
+ coverage_xml = f"""
+ {coverage_name}
+ Directory Harvest Test Coverage
+ {coverage_name}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Layer creation failed: {response.text}"
+
+ # Step 6: Verify the coverage was created successfully
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages/{coverage_name}.json"
+ )
+ assert response.status_code == 200, f"Failed to get coverage details: {response.text}"
+
+ coverage_data = response.json()["coverage"]
+ assert coverage_data["name"] == coverage_name
+ assert coverage_data["nativeName"] == coverage_name
+ assert coverage_data["enabled"] == True
+
+ # Step 7: Test WMS GetMap request
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}"
+ f"&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200, f"WMS GetMap failed: {wms_response.text}"
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
+
+
+def test_create_imagemosaic_empty_store_with_single_file_harvest():
+ """
+ Test creating an empty ImageMosaic store first, then harvesting a single file.
+ This tests the workflow: create store -> harvest single file -> create layer.
+ """
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "single_file_harvest"
+ store_name = "single_file_harvest_store"
+ coverage_name = "single_file_harvest_coverage"
+
+ # Clean up any existing workspace
+ geoserver.delete_workspace(workspace)
+
+ # Step 1: Create workspace
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201, f"Failed to create workspace: {response.text}"
+
+ # Step 2: Create ImageMosaic store
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_path = Path(tmp_dir)
+
+ # Create indexer.properties for single file
+ indexer_content = f"""MosaicCRS=EPSG\\:4326
+Name={coverage_name}
+PropertyCollectors=CRSExtractorSPI(crs),ResolutionExtractorSPI(resolution)
+Schema=*the_geom:Polygon,location:String,crs:String,resolution:String
+CanBeEmpty=true
+AbsolutePath=true"""
+
+ indexer_file = tmp_path / "indexer.properties"
+ indexer_file.write_text(indexer_content)
+
+ # Create datastore.properties (using JNDI)
+ datastore_content = """SPI=org.geotools.data.postgis.PostgisNGJNDIDataStoreFactory
+# JNDI data source
+jndiReferenceName=java:comp/env/jdbc/postgis
+
+#Boolean
+# perform only primary filter on bbox
+# Default Boolean.TRUE
+Loose\\ bbox=true
+
+#Boolean
+# use prepared statements
+#Default Boolean.FALSE
+preparedStatements=false
+"""
+ datastore_file = tmp_path / "datastore.properties"
+ datastore_file.write_text(datastore_content)
+
+ # Create ZIP file with both files
+ zip_file = tmp_path / "mosaic-single-config.zip"
+ with zipfile.ZipFile(zip_file, 'w') as zf:
+ zf.write(indexer_file, "indexer.properties")
+ zf.write(datastore_file, "datastore.properties")
+
+ # Upload ZIP to create ImageMosaic store
+ with open(zip_file, 'rb') as f:
+ zip_data = f.read()
+
+ response = geoserver.put_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/file.imagemosaic?configure=none",
+ data=zip_data,
+ headers={"Content-Type": "application/zip"}
+ )
+ assert response.status_code == 201, f"Failed to create ImageMosaic store: {response.text}"
+
+ # Step 3: Harvest single file
+ single_file_path = "/mnt/geoserver_data/sampledata/ne/NE1_LR_LC_SR_W_DR.tif"
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/external.imagemosaic",
+ data=single_file_path,
+ headers={"Content-Type": "text/plain"}
+ )
+ assert response.status_code in [201, 202], f"Failed to harvest file {single_file_path}: {response.text}"
+
+ # Step 4: List and create layer
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200, f"Failed to list coverages: {response.text}"
+
+ # Create layer/coverage
+ coverage_xml = f"""
+ {coverage_name}
+ Single File Harvest Test Coverage
+ {coverage_name}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Layer creation failed: {response.text}"
+
+ # Verify WMS works
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}"
+ f"&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200, f"WMS GetMap failed: {wms_response.text}"
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
+
+
+def test_create_imagemosaic_via_xml_store_creation():
+ """
+ Test creating an ImageMosaic store via XML store creation (not file upload).
+ This tests direct store creation pointing to a directory.
+ """
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "xml_store_creation"
+ store_name = "xml_store_creation_store"
+
+ # Clean up any existing workspace
+ geoserver.delete_workspace(workspace)
+
+ # Step 1: Create workspace
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201, f"Failed to create workspace: {response.text}"
+
+ # Step 2: Create ImageMosaic store via XML store creation
+ store_xml = f"""
+ {store_name}
+
+ {workspace}
+
+ ImageMosaic
+ true
+ /mnt/geoserver_data/sampledata/ne/pyramid/
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores",
+ data=store_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Store creation via XML failed: {response.text}"
+
+ # Step 3: List available coverages
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200, f"Failed to list coverages: {response.text}"
+ assert "coverageName" in response.text, f"No coverage found in response: {response.text}"
+
+ # Extract coverage name
+ import re
+ coverage_match = re.search(r'([^<]+)', response.text)
+ assert coverage_match, f"Could not extract coverage name from: {response.text}"
+ coverage_name = coverage_match.group(1)
+
+ # Create layer
+ coverage_xml = f"""
+ {coverage_name}
+ XML Store Creation Test Coverage
+ {coverage_name}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{store_name}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201, f"Layer creation failed: {response.text}"
+
+ # Verify WMS works
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage_name}"
+ f"&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200, f"WMS GetMap failed: {wms_response.text}"
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
diff --git a/acceptance_tests/tests/test_imagemosaic_cog.py b/acceptance_tests/tests/test_imagemosaic_cog.py
new file mode 100644
index 00000000..184b593a
--- /dev/null
+++ b/acceptance_tests/tests/test_imagemosaic_cog.py
@@ -0,0 +1,184 @@
+import os
+import tempfile
+import zipfile
+from pathlib import Path
+import pytest
+from geoservercloud import GeoServerCloud
+from conftest import GEOSERVER_URL
+
+
+def _create_imagemosaic(geoserver, workspace, coverage, granules, indexer_content, title="ImageMosaic Coverage"):
+ """Helper function to create an ImageMosaic with COG granules"""
+ # Delete and recreate workspace
+ geoserver.delete_workspace(workspace)
+ response = geoserver.create_workspace(workspace)
+ assert response.status_code == 201
+
+ # Create temporary directory for mosaic files
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_path = Path(tmp_dir)
+
+ # Create indexer.properties
+ indexer_file = tmp_path / "indexer.properties"
+ indexer_file.write_text(indexer_content)
+
+ # Create datastore.properties (using JNDI)
+ datastore_content = """SPI=org.geotools.data.postgis.PostgisNGJNDIDataStoreFactory
+# JNDI data source
+jndiReferenceName=java:comp/env/jdbc/postgis
+
+#Boolean
+# perform only primary filter on bbox
+# Default Boolean.TRUE
+Loose\\ bbox=true
+
+#Boolean
+# use prepared statements
+#Default Boolean.FALSE
+preparedStatements=false
+"""
+ datastore_file = tmp_path / "datastore.properties"
+ datastore_file.write_text(datastore_content)
+
+ # Create zip file
+ zip_file = tmp_path / f"{coverage}.zip"
+ with zipfile.ZipFile(zip_file, 'w') as zf:
+ zf.write(indexer_file, "indexer.properties")
+ zf.write(datastore_file, "datastore.properties")
+
+ # Create timeregex.properties if needed for time-based PropertyCollector
+ if "timeregex" in indexer_content:
+ # Regex pattern to extract date from MODIS filename format: 2018.01.01
+ timeregex_content = "regex=(?<=\\.)([0-9]{4}\\.[0-9]{2}\\.[0-9]{2})(?=\\.),format=yyyy.MM.dd"
+ timeregex_file = tmp_path / "timeregex.properties"
+ timeregex_file.write_text(timeregex_content)
+ zf.write(timeregex_file, "timeregex.properties")
+
+ # Create empty imagemosaic
+ with open(zip_file, 'rb') as f:
+ zip_data = f.read()
+
+ response = geoserver.put_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{coverage}/file.imagemosaic?configure=none",
+ data=zip_data,
+ headers={"Content-Type": "application/zip"}
+ )
+ assert response.status_code == 201
+
+ # Add granules
+ for uri in granules:
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{coverage}/remote.imagemosaic",
+ data=uri,
+ headers={"Content-Type": "text/plain"}
+ )
+ # Accept both 202 (Accepted) and 201 (Created) as valid responses
+ assert response.status_code in [201, 202]
+
+ # Initialize the store (list available coverages)
+ response = geoserver.get_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{coverage}/coverages.xml?list=all"
+ )
+ assert response.status_code == 200
+
+ # Verify coverage name in response
+ response_text = response.text
+ assert f"{coverage}" in response_text
+
+ # Configure the coverage
+ coverage_xml = f"""
+ {coverage}
+ {title}
+ {coverage}
+ true
+"""
+
+ response = geoserver.post_request(
+ f"/rest/workspaces/{workspace}/coveragestores/{coverage}/coverages",
+ data=coverage_xml,
+ headers={"Content-Type": "text/xml"}
+ )
+ assert response.status_code == 201
+
+ # Verify the coverage was created
+ response = geoserver.get_request(f"/rest/workspaces/{workspace}/coveragestores/{coverage}/coverages/{coverage}.json")
+ assert response.status_code == 200
+
+ # Verify coverage properties
+ coverage_data = response.json()["coverage"]
+ assert coverage_data["name"] == coverage
+ assert coverage_data["nativeName"] == coverage
+ assert coverage_data["enabled"] == True
+ assert coverage_data["title"] == title
+
+ # Test WMS GetMap request
+ wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage}&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326"
+ )
+ assert wms_response.status_code == 200
+ assert wms_response.headers.get("content-type").startswith("image/png")
+
+ return coverage_data
+
+
+def test_create_imagemosaic_landshallow_topo():
+ """Test creating an ImageMosaic coverage store with multiple COG granules"""
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "s3cog_public"
+ coverage = "land_shallow_topo_http"
+
+ # HTTP granules
+ granules = [
+ "https://test-data-cog-public.s3.amazonaws.com/public/land_shallow_topo_21600_NE_cog.tif",
+ "https://test-data-cog-public.s3.amazonaws.com/public/land_shallow_topo_21600_NW_cog.tif",
+ "https://test-data-cog-public.s3.amazonaws.com/public/land_shallow_topo_21600_SE_cog.tif",
+ "https://test-data-cog-public.s3.amazonaws.com/public/land_shallow_topo_21600_SW_cog.tif",
+ ]
+
+ # Create indexer.properties
+ indexer_content = f"""Cog=true
+CogRangeReader=it.geosolutions.imageioimpl.plugins.cog.HttpRangeReader
+Schema=*the_geom:Polygon,location:String
+CanBeEmpty=true
+Name={coverage}"""
+
+ _create_imagemosaic(geoserver, workspace, coverage, granules, indexer_content, "Land Shallow Topo HTTP")
+
+ # Cleanup
+ geoserver.delete_workspace(workspace)
+
+
+@pytest.mark.skip(reason="Takes too long - enable for full testing")
+def test_create_imagemosaic_modis():
+ """Test creating a MODIS ImageMosaic coverage with time dimension (reproduces official tutorial)"""
+ geoserver = GeoServerCloud(GEOSERVER_URL)
+ workspace = "modis_cog"
+ coverage = "modisvi"
+
+ # MODIS COG datasets from NASA EarthData
+ modis_granules = [
+ "https://modis-vi-nasa.s3-us-west-2.amazonaws.com/MOD13A1.006/2018.01.01.tif",
+ "https://modis-vi-nasa.s3-us-west-2.amazonaws.com/MOD13A1.006/2018.01.17.tif",
+ ]
+
+ # Create indexer.properties (based on MODIS tutorial)
+ indexer_content = f"""Cog=true
+PropertyCollectors=TimestampFileNameExtractorSPI[timeregex](time)
+TimeAttribute=time
+Schema=*the_geom:Polygon,location:String,time:java.util.Date
+CanBeEmpty=true
+Name={coverage}"""
+
+ coverage_data = _create_imagemosaic(geoserver, workspace, coverage, modis_granules, indexer_content, "MODIS Vegetation Index")
+
+ # Additional test for time-based query (since MODIS has time dimension)
+ time_wms_response = geoserver.get_request(
+ f"/wms?SERVICE=WMS&VERSION=1.1.0&REQUEST=GetMap&LAYERS={workspace}:{coverage}&STYLES=&BBOX=-180,-90,180,90&WIDTH=256&HEIGHT=256&FORMAT=image/png&SRS=EPSG:4326&TIME=2018-01-01"
+ )
+ assert time_wms_response.status_code == 200
+ assert time_wms_response.headers.get("content-type").startswith("image/png")
+
+ # Cleanup
+ #geoserver.delete_workspace(workspace)
+
+
diff --git a/compose/acceptance.yml b/compose/acceptance.yml
index 7cff95e0..9d37a692 100644
--- a/compose/acceptance.yml
+++ b/compose/acceptance.yml
@@ -8,6 +8,8 @@ services:
restart: always
volumes:
- ./acceptance_pg_entrypoint:/docker-entrypoint-initdb.d:ro
+ ports:
+ - "5433:5432" # Expose on port 5433 to avoid conflict with local PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U geodata"]
interval: 30s
@@ -22,7 +24,7 @@ services:
memory: 512M
acceptance:
- image: acceptance:${TAG}
+ image: geoservercloud/acceptance:latest
user: ${GS_USER}
depends_on:
geodatabase:
diff --git a/compose/acceptance_datadir b/compose/acceptance_datadir
index bd11b0fb..87e0d706 100755
--- a/compose/acceptance_datadir
+++ b/compose/acceptance_datadir
@@ -3,7 +3,8 @@
GSUID=$(id -u)
GSGID=$(id -g)
-GS_USER=$GSUID:$GSGID COMPOSE_PROJECT_NAME=gscloud-acceptance-datadir \
+GS_USER="$GSUID:$GSGID" \
+COMPOSE_PROJECT_NAME=gscloud-acceptance-datadir \
docker compose \
-f compose.yml \
-f catalog-datadir.yml \
diff --git a/compose/acceptance_jdbcconfig b/compose/acceptance_jdbcconfig
index 753a0b56..d738dc48 100755
--- a/compose/acceptance_jdbcconfig
+++ b/compose/acceptance_jdbcconfig
@@ -1,5 +1,9 @@
#/bin/bash
+GSUID=$(id -u)
+GSGID=$(id -g)
+
+GS_USER="$GSUID:$GSGID" \
COMPOSE_PROJECT_NAME=gscloud-acceptance-jdbcconfig \
docker compose \
-f compose.yml \
diff --git a/compose/acceptance_pgconfig b/compose/acceptance_pgconfig
index d8197823..d532e0f6 100755
--- a/compose/acceptance_pgconfig
+++ b/compose/acceptance_pgconfig
@@ -1,5 +1,9 @@
#/bin/bash
+GSUID=$(id -u)
+GSGID=$(id -g)
+
+GS_USER="$GSUID:$GSGID" \
COMPOSE_PROJECT_NAME=gscloud-acceptance-pgconfig \
docker compose \
-f compose.yml \
diff --git a/compose/catalog-datadir.yml b/compose/catalog-datadir.yml
index 9290f8b5..5a48a351 100644
--- a/compose/catalog-datadir.yml
+++ b/compose/catalog-datadir.yml
@@ -28,8 +28,8 @@ x-geoserver-env: &geoserver_environment
services:
init-datadir:
image: alpine:3.18.4
- user: ${GS_USER}
- command: sh -c "cd /opt/app/data_directory; if [ ! -f global.xml ]; then tar xvzf /tmp/datadir.tgz; fi"
+ user: root
+ command: sh -c "cd /opt/app/data_directory; if [ ! -f global.xml ]; then tar xvzf /tmp/datadir.tgz; fi; chown -R ${GS_USER} /opt/app/data_directory"
volumes:
- data_directory:/opt/app/data_directory
- ./catalog-datadir.tgz:/tmp/datadir.tgz
diff --git a/compose/compose.yml b/compose/compose.yml
index c74bfb43..4f62de90 100644
--- a/compose/compose.yml
+++ b/compose/compose.yml
@@ -1,9 +1,12 @@
+volumes:
+ # geowebcache tiles shared volume
+ geowebcache_data:
+ # geoserver data files shared volume (not datadir, data)
+ geoserver_data:
+
include:
- ./infra.yml
-volumes:
- geowebcache_data:
-
x-gs-dependencies: &gs-dependencies
rabbitmq:
condition: service_healthy
@@ -17,8 +20,19 @@ x-gs-dependencies: &gs-dependencies
postgis:
condition: service_started
required: true
+ init-test-data:
+ condition: service_completed_successfully
+ required: true
services:
+ init-test-data:
+ image: alpine:3.18.4
+ user: root
+ volumes:
+ - geoserver_data:/mnt/geoserver_data
+ - geowebcache_data:/mnt/geowebcache_data
+ - ./sampledata.tgz:/tmp/sampledata.tgz
+ command: sh -c "chown -R ${GS_USER} /mnt/geoserver_data /mnt/geowebcache_data && cd /mnt/geoserver_data && if [ ! -d sampledata ]; then tar xvzf /tmp/sampledata.tgz && chown -R ${GS_USER} sampledata; fi"
acl:
image: ${ACL_REPOSITORY}/geoserver-acl:${ACL_TAG}
diff --git a/compose/datadir b/compose/datadir
index bf78d4ce..c4c14a37 100755
--- a/compose/datadir
+++ b/compose/datadir
@@ -1,3 +1,7 @@
#/bin/sh
+GSUID=$(id -u)
+GSGID=$(id -g)
+
+GS_USER="$GSUID:$GSGID" \
docker compose -f compose.yml -f catalog-datadir.yml $@
diff --git a/compose/jdbcconfig b/compose/jdbcconfig
index 7b5bb2ac..76d3e360 100755
--- a/compose/jdbcconfig
+++ b/compose/jdbcconfig
@@ -1,3 +1,7 @@
#/bin/sh
+GSUID=$(id -u)
+GSGID=$(id -g)
+
+GS_USER="$GSUID:$GSGID" \
docker compose -f compose.yml -f catalog-jdbcconfig.yml $@
diff --git a/compose/pgconfig b/compose/pgconfig
index 2111ef88..5546f868 100755
--- a/compose/pgconfig
+++ b/compose/pgconfig
@@ -1,3 +1,7 @@
#/bin/sh
+GSUID=$(id -u)
+GSGID=$(id -g)
+
+GS_USER="$GSUID:$GSGID" \
docker compose -f compose.yml -f catalog-pgconfig.yml $@
diff --git a/compose/sampledata.tgz b/compose/sampledata.tgz
new file mode 100644
index 00000000..35e8ad27
Binary files /dev/null and b/compose/sampledata.tgz differ
diff --git a/compose/templates.yml b/compose/templates.yml
index 7f1327e3..115fe6cd 100644
--- a/compose/templates.yml
+++ b/compose/templates.yml
@@ -1,13 +1,14 @@
# Define reusable volume mounts as an anchor
x-geoserver-volume-mounts: &geoserver_volumes
- - geowebcache_data:/data/geowebcache
+ - geowebcache_data:/mnt/geowebcache_data
+ - geoserver_data:/mnt/geoserver_data
# Define reusable environment variables
x-geoserver-env: &geoserver_environment
SPRING_PROFILES_ACTIVE: "${GEOSERVER_DEFAULT_PROFILES}"
# Enable the PostGIS JNDI datasource (for development purposes)
JNDI_POSTGIS_ENABLED: true
- GEOWEBCACHE_CACHE_DIR: /data/geowebcache
+ GEOWEBCACHE_CACHE_DIR: /mnt/geowebcache_data
JAVA_OPTS: "${JAVA_OPTS_GEOSERVER}"
services: