Skip to content

Commit bf975de

Browse files
committed
Refactored scripts.
1 parent 16631e1 commit bf975de

File tree

18 files changed

+3275
-1211
lines changed

18 files changed

+3275
-1211
lines changed

.github/workflows/ci.yml

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
branches: [ "main" ]
66
pull_request:
77
branches: [ "main" ]
8+
workflow_dispatch:
9+
inputs:
10+
debug_enabled:
11+
type: boolean
12+
description: 'Run with upterm debugging enabled (requires SSH key in secrets)'
13+
required: false
14+
default: false
815

916
env:
1017
HELM_VERSION: v3.15.2
@@ -27,7 +34,7 @@ jobs:
2734
version: ${{ env.HELM_VERSION }}
2835

2936
- name: Setup Helm dependencies
30-
run: ./scripts/deploy.sh setup
37+
run: ./scripts/deploy.sh setup --deps-only
3138

3239
- name: Install ajv-cli
3340
run: npm install -g ajv-cli ajv-formats
@@ -39,7 +46,7 @@ jobs:
3946
run: make validate-schema
4047

4148
- name: Run Helm unit tests
42-
run: make tests
49+
run: make test-helm
4350

4451
integration-tests:
4552
name: Integration tests
@@ -49,6 +56,16 @@ jobs:
4956
steps:
5057
- uses: actions/checkout@v5
5158

59+
- name: Set up Python
60+
uses: actions/setup-python@v5
61+
with:
62+
python-version: '3.12'
63+
64+
- name: Install Python dependencies
65+
run: |
66+
python -m pip install --upgrade pip
67+
pip install httpx psycopg2-binary pytest
68+
5269
- name: Start K3s cluster
5370
uses: jupyterhub/action-k3s-helm@v4
5471
with:
@@ -89,21 +106,91 @@ jobs:
89106
echo "=== eoAPI Deployment ==="
90107
export RELEASE_NAME="${RELEASE_NAME}"
91108
export PGO_VERSION="${{ env.PGO_VERSION }}"
92-
export CI_MODE=true
93109
94-
# Deploy using consolidated script with CI mode
95-
./scripts/deploy.sh --ci
110+
# Deploy using consolidated script with ingress enabled for testing (Traefik in K3s)
111+
./scripts/deploy.sh deploy --namespace "${RELEASE_NAME}" --release "${RELEASE_NAME}" \
112+
--set ingress.className=traefik \
113+
--debug
114+
115+
- name: Debug session after deployment
116+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
117+
uses: lhotari/action-upterm@v1
118+
with:
119+
limit-access-to-keys: ${{ secrets.UPTERM_SSH_KEY }}
120+
wait-timeout-minutes: 30
96121

97122
- name: Validate deployment
98123
run: |
99124
echo "=== Post-deployment validation ==="
125+
export NAMESPACE="${RELEASE_NAME}"
100126
./scripts/test.sh check-deployment
101127
128+
- name: Wait for services to be ready
129+
run: |
130+
echo "=== Waiting for Services to be Ready ==="
131+
kubectl wait --for=condition=available deployment/"${RELEASE_NAME}"-stac -n "${RELEASE_NAME}" --timeout=300s
132+
kubectl wait --for=condition=available deployment/"${RELEASE_NAME}"-raster -n "${RELEASE_NAME}" --timeout=300s
133+
kubectl wait --for=condition=available deployment/"${RELEASE_NAME}"-vector -n "${RELEASE_NAME}" --timeout=300s
134+
135+
# Get the K3s node IP for ingress access
136+
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
137+
echo "Node IP: $NODE_IP"
138+
139+
# Wait for ingress to be ready
140+
echo "=== Waiting for Ingress to be Ready ==="
141+
kubectl get ingress -n "${RELEASE_NAME}"
142+
143+
# Wait for Traefik to pick up the ingress rules
144+
sleep 10
145+
146+
# Test connectivity through ingress using node IP
147+
echo "=== Testing API connectivity through ingress ==="
148+
for i in {1..30}; do
149+
if curl -s "http://${NODE_IP}/stac/_mgmt/ping" 2>/dev/null; then
150+
echo "✅ STAC API accessible through ingress"
151+
break
152+
fi
153+
echo "Waiting for STAC API... (attempt $i/30)"
154+
sleep 3
155+
done
156+
157+
for i in {1..30}; do
158+
if curl -s "http://${NODE_IP}/raster/healthz" 2>/dev/null; then
159+
echo "✅ Raster API accessible through ingress"
160+
break
161+
fi
162+
echo "Waiting for Raster API... (attempt $i/30)"
163+
sleep 3
164+
done
165+
166+
for i in {1..30}; do
167+
if curl -s "http://${NODE_IP}/vector/healthz" 2>/dev/null; then
168+
echo "✅ Vector API accessible through ingress"
169+
break
170+
fi
171+
echo "Waiting for Vector API... (attempt $i/30)"
172+
sleep 3
173+
done
174+
175+
# Export NODE_IP for later steps
176+
echo "NODE_IP=$NODE_IP" >> "$GITHUB_ENV"
177+
102178
- name: Run integration tests
103179
run: |
104180
export RELEASE_NAME="$RELEASE_NAME"
181+
export NAMESPACE="$RELEASE_NAME"
182+
export STAC_ENDPOINT="http://${NODE_IP}/stac"
183+
export RASTER_ENDPOINT="http://${NODE_IP}/raster"
184+
export VECTOR_ENDPOINT="http://${NODE_IP}/vector"
105185
./scripts/test.sh integration --debug
106186
187+
- name: Debug session on test failure
188+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
189+
uses: lhotari/action-upterm@v1
190+
with:
191+
limit-access-to-keys: ${{ secrets.UPTERM_SSH_KEY }}
192+
wait-timeout-minutes: 30
193+
107194
- name: Debug failed deployment
108195
if: failure()
109196
run: |
@@ -112,8 +199,8 @@ jobs:
112199
- name: Cleanup
113200
if: always()
114201
run: |
115-
helm uninstall "$RELEASE_NAME" -n eoapi || true
116-
kubectl delete namespace eoapi || true
202+
helm uninstall "$RELEASE_NAME" -n "$RELEASE_NAME" || true
203+
kubectl delete namespace "$RELEASE_NAME" || true
117204
validate-docs:
118205
name: Validate documentation
119206
runs-on: ubuntu-latest

.github/workflows/tests/test_notifications.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ def test_cloudevents_sink_logs_show_startup():
115115
pytest.skip("Cannot get Knative CloudEvents sink logs")
116116

117117
logs = result.stdout
118-
assert "listening on port" in logs, "Knative CloudEvents sink should have started successfully"
118+
assert "listening on port" in logs, (
119+
"Knative CloudEvents sink should have started successfully"
120+
)
119121

120122

121123
def test_eoapi_notifier_logs_show_connection():

.github/workflows/tests/test_raster.py

Lines changed: 104 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""test EOapi."""
2-
import httpx
2+
33
import os
44

5+
import httpx
6+
57
# better timeouts
68
timeout = httpx.Timeout(15.0, connect=60.0)
79
if bool(os.getenv("IGNORE_SSL_VERIFICATION", False)):
@@ -21,7 +23,10 @@ def test_raster_api(raster_endpoint):
2123

2224
def test_mosaic_api(raster_endpoint):
2325
"""test mosaic."""
24-
query = {"collections": ["noaa-emergency-response"], "filter-lang": "cql-json"}
26+
query = {
27+
"collections": ["noaa-emergency-response"],
28+
"filter-lang": "cql-json",
29+
}
2530
resp = client.post(f"{raster_endpoint}/searches/register", json=query)
2631
assert resp.headers["content-type"] == "application/json"
2732
assert resp.status_code == 200
@@ -30,7 +35,9 @@ def test_mosaic_api(raster_endpoint):
3035

3136
searchid = resp.json()["id"]
3237

33-
resp = client.get(f"{raster_endpoint}/searches/{searchid}/point/-85.6358,36.1624/assets")
38+
resp = client.get(
39+
f"{raster_endpoint}/searches/{searchid}/point/-85.6358,36.1624/assets"
40+
)
3441
assert resp.status_code == 200
3542
assert len(resp.json()) == 1
3643
assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"]
@@ -91,51 +98,87 @@ def test_mosaic_search(raster_endpoint):
9198
# register some fake mosaic
9299
searches = [
93100
{
94-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection1"]},
101+
"filter": {
102+
"op": "=",
103+
"args": [{"property": "collection"}, "collection1"],
104+
},
95105
"metadata": {"owner": "vincent"},
96106
},
97107
{
98-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection2"]},
108+
"filter": {
109+
"op": "=",
110+
"args": [{"property": "collection"}, "collection2"],
111+
},
99112
"metadata": {"owner": "vincent"},
100113
},
101114
{
102-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection3"]},
115+
"filter": {
116+
"op": "=",
117+
"args": [{"property": "collection"}, "collection3"],
118+
},
103119
"metadata": {"owner": "vincent"},
104120
},
105121
{
106-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection4"]},
122+
"filter": {
123+
"op": "=",
124+
"args": [{"property": "collection"}, "collection4"],
125+
},
107126
"metadata": {"owner": "vincent"},
108127
},
109128
{
110-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection5"]},
129+
"filter": {
130+
"op": "=",
131+
"args": [{"property": "collection"}, "collection5"],
132+
},
111133
"metadata": {"owner": "vincent"},
112134
},
113135
{
114-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection6"]},
136+
"filter": {
137+
"op": "=",
138+
"args": [{"property": "collection"}, "collection6"],
139+
},
115140
"metadata": {"owner": "vincent"},
116141
},
117142
{
118-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection7"]},
143+
"filter": {
144+
"op": "=",
145+
"args": [{"property": "collection"}, "collection7"],
146+
},
119147
"metadata": {"owner": "vincent"},
120148
},
121149
{
122-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection8"]},
150+
"filter": {
151+
"op": "=",
152+
"args": [{"property": "collection"}, "collection8"],
153+
},
123154
"metadata": {"owner": "sean"},
124155
},
125156
{
126-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection9"]},
157+
"filter": {
158+
"op": "=",
159+
"args": [{"property": "collection"}, "collection9"],
160+
},
127161
"metadata": {"owner": "sean"},
128162
},
129163
{
130-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection10"]},
164+
"filter": {
165+
"op": "=",
166+
"args": [{"property": "collection"}, "collection10"],
167+
},
131168
"metadata": {"owner": "drew"},
132169
},
133170
{
134-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection11"]},
171+
"filter": {
172+
"op": "=",
173+
"args": [{"property": "collection"}, "collection11"],
174+
},
135175
"metadata": {"owner": "drew"},
136176
},
137177
{
138-
"filter": {"op": "=", "args": [{"property": "collection"}, "collection12"]},
178+
"filter": {
179+
"op": "=",
180+
"args": [{"property": "collection"}, "collection12"],
181+
},
139182
"metadata": {"owner": "drew"},
140183
},
141184
]
@@ -158,9 +201,16 @@ def test_mosaic_search(raster_endpoint):
158201

159202
links = resp.json()["links"]
160203
assert len(links) == 2
161-
assert links[0]["rel"] == "self"
162-
assert links[1]["rel"] == "next"
163-
assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=10&offset=10"
204+
# Find links by rel type
205+
link_rels = {link["rel"]: link["href"] for link in links}
206+
assert "self" in link_rels
207+
assert "next" in link_rels
208+
# Check if href contains the expected path (works with or without ROOT_PATH)
209+
next_href = link_rels["next"]
210+
assert (
211+
next_href.endswith("/searches/list?limit=10&offset=10")
212+
or next_href == f"{raster_endpoint}/searches/list?limit=10&offset=10"
213+
)
164214

165215
resp = client.get(
166216
f"{raster_endpoint}/searches/list", params={"limit": 1, "offset": 1}
@@ -172,34 +222,59 @@ def test_mosaic_search(raster_endpoint):
172222

173223
links = resp.json()["links"]
174224
assert len(links) == 3
175-
assert links[0]["rel"] == "self"
176-
assert links[0]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=1"
177-
assert links[1]["rel"] == "next"
178-
assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=2"
179-
assert links[2]["rel"] == "prev"
180-
assert links[2]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=0"
225+
# Find links by rel type
226+
link_rels = {link["rel"]: link["href"] for link in links}
227+
assert "self" in link_rels
228+
assert "next" in link_rels
229+
assert "prev" in link_rels
230+
# Check if hrefs contain the expected paths (works with or without ROOT_PATH)
231+
assert (
232+
link_rels["prev"].endswith("/searches/list?limit=1&offset=0")
233+
or link_rels["prev"]
234+
== f"{raster_endpoint}/searches/list?limit=1&offset=0"
235+
)
236+
assert (
237+
link_rels["self"].endswith("/searches/list?limit=1&offset=1")
238+
or link_rels["self"]
239+
== f"{raster_endpoint}/searches/list?limit=1&offset=1"
240+
)
241+
assert (
242+
link_rels["next"].endswith("/searches/list?limit=1&offset=2")
243+
or link_rels["next"]
244+
== f"{raster_endpoint}/searches/list?limit=1&offset=2"
245+
)
181246

182247
# Filter on mosaic metadata
183-
resp = client.get(f"{raster_endpoint}/searches/list", params={"owner": "vincent"})
248+
resp = client.get(
249+
f"{raster_endpoint}/searches/list", params={"owner": "vincent"}
250+
)
184251
assert resp.status_code == 200
185252
assert resp.json()["context"]["matched"] == 7
186253
assert resp.json()["context"]["limit"] == 10
187254
assert resp.json()["context"]["returned"] == 7
188255

189256
# sortBy
190-
resp = client.get(f"{raster_endpoint}/searches/list", params={"sortby": "lastused"})
257+
resp = client.get(
258+
f"{raster_endpoint}/searches/list", params={"sortby": "lastused"}
259+
)
191260
assert resp.status_code == 200
192261

193-
resp = client.get(f"{raster_endpoint}/searches/list", params={"sortby": "usecount"})
262+
resp = client.get(
263+
f"{raster_endpoint}/searches/list", params={"sortby": "usecount"}
264+
)
194265
assert resp.status_code == 200
195266

196-
resp = client.get(f"{raster_endpoint}/searches/list", params={"sortby": "-owner"})
267+
resp = client.get(
268+
f"{raster_endpoint}/searches/list", params={"sortby": "-owner"}
269+
)
197270
assert resp.status_code == 200
198271
assert (
199272
"owner" not in resp.json()["searches"][0]["search"]["metadata"]
200273
) # some mosaic don't have owners
201274

202-
resp = client.get(f"{raster_endpoint}/searches/list", params={"sortby": "owner"})
275+
resp = client.get(
276+
f"{raster_endpoint}/searches/list", params={"sortby": "owner"}
277+
)
203278
assert resp.status_code == 200
204279
assert "owner" in resp.json()["searches"][0]["search"]["metadata"]
205280

0 commit comments

Comments
 (0)