Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDAL base image #537

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ jobs:
run: |
make build-image

- name: Install CI dependencies
run: python3 -m pip install --user --requirement=ci/requirements.txt

- name: Run acceptance tests datadir
run: |
make acceptance-tests-datadir

- name: Print docker compose logs datadir
run: (cd compose && c2cciutils-docker-logs)
if: always()

- name: Cleanup acceptance tests datadir
run: |
make clean-acceptance-tests-datadir

# FIXME: fix pgconfig discrepancies before reactivating
# - name: Run acceptance tests pgconfig
# run: |
# make acceptance-tests-pgconfig

# - name: Print docker compose logs pgconfig
# run: (cd compose && c2cciutils-docker-logs)
# if: always()

# - name: Cleanup acceptance tests pgconfig
# run: |
# make clean-acceptance-tests-pgconfig

- name: Remove project jars from cached repository
run: |
rm -rf ~/.m2/repository/org/geoserver
Expand Down
33 changes: 32 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
all: install test build-image

TAG=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
TAG=$(shell mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
COSIGN_PASSWORD := $(COSIGN_PASSWORD)
COMPOSE_PGCONFIG_OPTIONS ?= -f compose.yml -f catalog-pgconfig.yml
COMPOSE_DATADIR_OPTIONS ?= -f compose.yml -f catalog-datadir.yml
COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS ?= $(COMPOSE_PGCONFIG_OPTIONS) -f acceptance.yml
COMPOSE_ACCEPTANCE_DATADIR_OPTIONS ?= $(COMPOSE_DATADIR_OPTIONS) -f acceptance.yml
UID=$(shell id -u)
GID=$(shell id -g)

clean:
./mvnw clean
Expand Down Expand Up @@ -78,3 +84,28 @@ verify-image:
fi; \
done'

.PHONY: build-acceptance
build-acceptance:
docker build --tag=acceptance:$(TAG) acceptance_tests

.PHONY: acceptance-tests-pgconfig
acceptance-tests-pgconfig:
acceptance-tests-pgconfig: build-acceptance
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) up -d)
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes')

.PHONY: clean-acceptance-tests-pgconfig
clean-acceptance-tests-pgconfig:
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) down -v)

.PHONY: acceptance-tests-datadir
acceptance-tests-datadir:
acceptance-tests-datadir: build-acceptance
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d)
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes')

.PHONY: clean-acceptance-tests-datadir
clean-acceptance-tests-datadir:
(cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v)
rm -rf compose/catalog-datadir/*
touch compose/catalog-datadir/.keep
3 changes: 3 additions & 0 deletions acceptance_tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ignore all python cache files recursively
**/__pycache__/
poetry.lock
22 changes: 22 additions & 0 deletions acceptance_tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM ubuntu:24.04 AS base

RUN apt-get update \
&& apt-get upgrade --assume-yes \
&& apt-get install --assume-yes --no-install-recommends \
vim curl jq libmagic1 zip python3-pip libpq-dev python3-dev gcc \
&& rm -rf /var/lib/apt/lists/* \
&& rm /usr/lib/python*/EXTERNALLY-MANAGED

COPY . /acceptance_tests

WORKDIR /acceptance_tests
RUN python3 -m pip install --disable-pip-version-check .

COPY entrypoint.py /bin/entrypoint.py

ENV PYTHONUNBUFFERED=1
ENTRYPOINT ["/bin/entrypoint.py"]

HEALTHCHECK --interval=5s --start-period=15s --retries=20 CMD test -f /tmp/healthcheck || exit 1

CMD ["sleep", "infinity"]
18 changes: 18 additions & 0 deletions acceptance_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# GeoServer Cloud acceptance tests

## Requirements

[Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)

## Installation

```shell
poetry install
```

# Run the tests
First start the docker composition then run:

```shell
GEOSERVER_URL=http://localhost:9090/geoserver/cloud poetry run pytest -vvv .
```
72 changes: 72 additions & 0 deletions acceptance_tests/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import time
import os
import requests
import sys

# Set variables
start_time = time.time()

# Set a maximum timeout from the environment or use 60 seconds as default
max_time = int(os.getenv('MAX_TIMEOUT', 60))

# Set the GEOSERVER_URL from the environment or use the default value
GEOSERVER_URL = os.getenv('GEOSERVER_URL', 'http://gateway:8080/geoserver/cloud')
GEOSERVER_USERNAME = os.getenv('GEOSERVER_USERNAME', 'admin')
GEOSERVER_PASSWORD = os.getenv('GEOSERVER_PASSWORD', 'geoserver')

# is we want to start directly with the passed command
IGNORE_HEALTH_CHECK = os.getenv('IGNORE_HEALTH_CHECK', False)

# Timeout function
def timeout():
current_time = time.time()
if current_time - start_time > max_time:
return True
return False

# Array of endpoints to check
endpoints = [
f"{GEOSERVER_URL}/wms?SERVICE=WMS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wfs?SERVICE=WFS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wps?SERVICE=WPS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/wcs?SERVICE=WCS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/ows?SERVICE=WMS&REQUEST=GetCapabilities",
f"{GEOSERVER_URL}/gwc",
f"{GEOSERVER_URL}/rest",
]

if not IGNORE_HEALTH_CHECK:
# Loop through each endpoint and check if it's available
for endpoint in endpoints:
print(f"Waiting for {endpoint} to be available...")
if timeout():
print("Timeout")
break

while True:
try:
# Make a request to the endpoint
response = requests.get(endpoint, auth=(GEOSERVER_USERNAME, GEOSERVER_PASSWORD))
if response.status_code == 200:
print(f"{endpoint} is up")
break
else:
print(f"{endpoint} returned status code {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"{endpoint} is not available - retrying...")

if timeout():
print("Timeout reached")
break

time.sleep(1)

# create /tmp/healthcheck file to signal that the healthcheck is done
with open("/tmp/healthcheck", "w") as f:
f.write("done")
# Execute the command passed to the script anyway, this is useful for
# running the tests and see what breaks
if len(sys.argv) > 1:
command = sys.argv[1:]
os.execvp(command[0], command)
19 changes: 19 additions & 0 deletions acceptance_tests/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "acceptance-tests"
version = "0.1.0"
description = "todo"
authors = ["todo"]
readme = "README.md"
packages = [{ include = "tests" }]

[tool.poetry.dependencies]
python = "^3.10"
pytest = "^8.3.3"
psycopg2-binary = "^2.9.9"
geoservercloud = "^0.2.5"
sqlalchemy = "^2.0.35"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
60 changes: 60 additions & 0 deletions acceptance_tests/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
from pathlib import Path

import pytest
import sqlalchemy
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"
WORKSPACE = "test_workspace"
DATASTORE = "test_datastore"


@pytest.fixture(scope="session", autouse=True)
def engine():
yield sqlalchemy.create_engine(
f"postgresql://{PGUSER}:{PGPASSWORD}@{PGHOST}:{PGPORT}/{PGDATABASE}",
)


@pytest.fixture(scope="session", autouse=True)
def db_session(engine):
with engine.connect() as connection:
connection.execute(
sqlalchemy.sql.text(f"CREATE SCHEMA IF NOT EXISTS {PGSCHEMA}")
)
connection.execute(sqlalchemy.sql.text(f"SET SEARCH_PATH = {PGSCHEMA}"))
connection.commit()
yield connection
connection.execute(
sqlalchemy.sql.text(f"DROP SCHEMA IF EXISTS {PGSCHEMA} CASCADE")
)
connection.commit()


@pytest.fixture(scope="module")
def geoserver():
geoserver = GeoServerCloud(GEOSERVER_URL)
geoserver.recreate_workspace(WORKSPACE, set_default_workspace=True)
geoserver.create_pg_datastore(
workspace=WORKSPACE,
datastore=DATASTORE,
pg_host=PGHOST,
pg_port=PGPORT,
pg_db=PGDATABASE,
pg_user=PGUSER,
pg_password=PGPASSWORD,
pg_schema=PGSCHEMA,
set_default_datastore=True,
)
geoserver.publish_workspace(WORKSPACE)
yield geoserver
geoserver.delete_workspace(WORKSPACE)
15 changes: 15 additions & 0 deletions acceptance_tests/tests/lib/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path


def write_actual_image(response, tag):
file = Path(f"/tmp/{tag}_actual.png")
file.parent.mkdir(parents=True, exist_ok=True)
with open(file, "wb") as fs:
fs.write(response.read())


def compare_images(dir, tag):
actual = f"/tmp/{tag}_actual.png"
expected = f"{dir}/{tag}_expected.png"
with open(actual, "rb") as fs1, open(expected, "rb") as fs2:
assert fs1.read() == fs2.read()
Binary file added acceptance_tests/tests/resources/getmap_expected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions acceptance_tests/tests/resources/localized_labels.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:se="http://www.opengis.net/se"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>localized_labels</Name>
<UserStyle>
<Name>localized_labels</Name>
<FeatureTypeStyle>
<Rule>
<Name>Localized labels</Name>
<TextSymbolizer>
<Label>
<ogc:Function name="Recode">
<ogc:Function name="language"/>
<ogc:Literal/>
<ogc:PropertyName>label_default</ogc:PropertyName>
<ogc:Literal>de</ogc:Literal>
<ogc:PropertyName>label_de</ogc:PropertyName>
<ogc:Literal>fr</ogc:Literal>
<ogc:PropertyName>label_fr</ogc:PropertyName>
</ogc:Function>
</Label>
<Fill>
<CssParameter name="fill">#000000</CssParameter>
</Fill>
</TextSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
37 changes: 37 additions & 0 deletions acceptance_tests/tests/resources/localized_no_default.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:se="http://www.opengis.net/se"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>localized_style</Name>
<UserStyle>
<Name>localized_no_default</Name>
<FeatureTypeStyle>
<Rule>
<Name>Localized, no default value</Name>
<Title>
<Localized lang="en">English</Localized>
<Localized lang="de">Deutsch</Localized>
<Localized lang="fr">Français</Localized>
<Localized lang="it">Italiano</Localized>
</Title>
<PointSymbolizer>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Fill>
<CssParameter name="fill">#FF0000</CssParameter>
</Fill>
</Mark>
<Size>6</Size>
</Graphic>
</PointSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
Loading
Loading