Skip to content

Commit e0ab929

Browse files
committed
Add Transfer Script
1 parent c2c6a9a commit e0ab929

File tree

9 files changed

+503
-4
lines changed

9 files changed

+503
-4
lines changed

.github/workflows/build.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ jobs:
155155
- name: Load Torch Image
156156
run: docker load --input /tmp/torch.tar
157157

158+
158159
- name: Check out Git repository
159160
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
160161

@@ -177,12 +178,68 @@ jobs:
177178
178179
- name: Run Blackbox Integration Tests
179180
run: mvn -P blackbox-integration-tests -B verify
181+
182+
183+
script-integration-test:
184+
needs: build
185+
runs-on: ubuntu-24.04
186+
steps:
187+
- name: Download Torch Image
188+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
189+
with:
190+
name: torch-image
191+
path: /tmp
192+
193+
- name: Load Torch Image
194+
run: docker load --input /tmp/torch.tar
195+
196+
- name: Check out Git repository
197+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
180198

199+
- name: Start Docker Compose
200+
working-directory: .github/workflows/script-integration-test
201+
run: docker compose up -d
202+
203+
- name: Wait for source-fhir-server to become healthy
204+
run: |
205+
set -e
206+
207+
echo "⏳ Waiting for source-fhir-server container to become healthy..."
208+
209+
# Get the container name (adjust if you're not using default naming)
210+
CONTAINER=$(docker ps --filter "name=source-fhir-server" --format "{{.Names}}" | head -n1)
211+
212+
if [ -z "$CONTAINER" ]; then
213+
echo "❌ No container found matching source-fhir-server"
214+
docker ps -a
215+
exit 1
216+
fi
217+
218+
for i in {1..30}; do
219+
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER" || echo "unavailable")
220+
echo "Attempt $i - Status: $STATUS"
221+
if [ "$STATUS" == "healthy" ]; then
222+
echo "✅ $CONTAINER is healthy"
223+
exit 0
224+
fi
225+
sleep 10
226+
done
227+
228+
echo "❌ $CONTAINER did not become healthy in time"
229+
docker logs "$CONTAINER"
230+
exit 1
231+
232+
- name: Run integration script
233+
run: .github/workflows/script-integration-test/integration-test.sh
234+
235+
236+
181237
push-image:
182238
needs:
183239
- test
184240
- image-scan
185241
- blackbox-integration-tests
242+
- script-integration-test
186243
runs-on: ubuntu-24.04
187244
if: ${{ ! startsWith(github.head_ref, 'dependabot/')}}
188245

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
services:
2+
source-fhir-server:
3+
container_name: source-fhir-server
4+
image: samply/blaze:1.0
5+
environment:
6+
BASE_URL: http://source-fhir-server:8080
7+
LOG_LEVEL: debug
8+
JAVA_TOOL_OPTIONS: -Xmx2g
9+
ports:
10+
- "8083:8080"
11+
healthcheck:
12+
test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
13+
interval: 30s
14+
timeout: 10s
15+
retries: 5
16+
start_period: 40s
17+
18+
target-fhir-server:
19+
container_name: target-fhir-server
20+
image: samply/blaze:1.0
21+
environment:
22+
BASE_URL: http://target-fhir-server:8080
23+
LOG_LEVEL: debug
24+
JAVA_TOOL_OPTIONS: -Xmx2g
25+
ports:
26+
- "8084:8080"
27+
healthcheck:
28+
test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
29+
interval: 30s
30+
timeout: 10s
31+
retries: 5
32+
start_period: 40s
33+
torch:
34+
container_name: torch
35+
restart: unless-stopped
36+
image: torch:latest
37+
ports:
38+
- "8080"
39+
environment:
40+
TORCH_PROFILE_DIR: /app/structureDefinitions
41+
TORCH_MAPPING_CONSENT: /app/mappings/consent-mappings_fhir.json
42+
TORCH_MAPPING_TYPE_TO_CONSENT: /app/mappings/type_to_consent.json
43+
TORCH_FHIR_URL: http://source-fhir-server:8080/fhir
44+
TORCH_FHIR_MAX_CONNECTIONS: 4
45+
TORCH_RESULTS_DIR: /app/output
46+
LOG_LEVEL: debug
47+
TORCH_OUTPUT_FILE_SERVER_URL: http://localhost:8082
48+
TORCH_BASE_URL: http://localhost:8082
49+
TORCH_BATCHSIZE: 100
50+
TORCH_MAXCONCURRENCY: 4
51+
TORCH_MAPPINGSFILE: /app/ontology/mapping_cql.json
52+
TORCH_CONCEPTTREEFILE: /app/ontology/mapping_tree.json
53+
TORCH_USECQL: true
54+
JAVA_TOOL_OPTIONS: -Xmx4g
55+
volumes:
56+
- torch-data-store:/app/output
57+
58+
nginx:
59+
container_name: nginx
60+
restart: unless-stopped
61+
image: nginxinc/nginx-unprivileged:1.25.5-alpine
62+
ports:
63+
- "8082:8080"
64+
volumes:
65+
- torch-data-store:/app/output
66+
- ./nginx.conf:/etc/nginx/nginx.conf
67+
68+
volumes:
69+
torch-data-store:
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# 1. Install blazectl
5+
VERSION="1.0.0"
6+
URL="https://github.com/samply/blazectl/releases/download/v${VERSION}/blazectl-${VERSION}-linux-amd64.tar.gz"
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
10+
11+
echo "📦 Downloading blazectl ${VERSION}..."
12+
curl -LO "$URL"
13+
14+
echo "📂 Extracting binary..."
15+
tar xzf "blazectl-${VERSION}-linux-amd64.tar.gz"
16+
17+
echo "➡️ Installing to /usr/local/bin..."
18+
sudo mv blazectl /usr/local/bin/blazectl
19+
chmod +x /usr/local/bin/blazectl
20+
21+
echo "🔍 Installed version:"
22+
blazectl --version
23+
24+
rm blazectl-${VERSION}-linux-amd64.tar.gz
25+
26+
# 2. Upload BlazeBundle to source server (seed data)
27+
echo "➡️ Uploading initial data bundle..."
28+
29+
echo "Root dir: $ROOT_DIR"
30+
ls "$ROOT_DIR/src/test/resources/BlazeBundle.json"
31+
32+
if curl -s http://localhost:8083/fhir/metadata?_elements=software | jq -r '.software.name' | grep -iq blaze; then
33+
echo "✅ Source FHIR Server Live"
34+
else
35+
echo "❌ Source FHIR Server Not Working"
36+
exit 1
37+
fi
38+
39+
40+
echo "📤 Posting BlazeBundle.json to http://localhost:8083/fhir"
41+
curl -i -s \
42+
-X POST "http://localhost:8083/fhir" \
43+
-H "Content-Type: application/fhir+json" \
44+
--data-binary @"$ROOT_DIR/src/test/resources/BlazeBundle.json"
45+
46+
TARGET_SERVER=http://localhost:8084/fhir
47+
48+
# 3. Run extraction + transfer script
49+
echo "➡️ Running extraction and transfer..."
50+
TORCH_BASE_URL=http://localhost:8082 \
51+
"$ROOT_DIR/scripts/transfer-extraction-to-dup-fhir-server.sh" -c "$ROOT_DIR/src/test/resources/CRTDL/CRTDL_observation_all_fields_withoutReference.json" -t "$TARGET_SERVER"
52+
53+
# 4. Assertions – Patient count
54+
EXPECTED_PATIENT_COUNT=4
55+
ACTUAL_PATIENT_COUNT=$(curl -s "${TARGET_SERVER}/Patient?_summary=count" | jq '.total')
56+
57+
if [ "$ACTUAL_PATIENT_COUNT" -eq "$EXPECTED_PATIENT_COUNT" ]; then
58+
echo "✅ Patient count is correct: $ACTUAL_PATIENT_COUNT"
59+
else
60+
echo "❌ Patient count is $ACTUAL_PATIENT_COUNT, expected $EXPECTED_PATIENT_COUNT"
61+
exit 1
62+
fi
63+
64+
# 5. Assertions – Observation count
65+
EXPECTED_OBSERVATION_COUNT=4
66+
ACTUAL_OBSERVATION_COUNT=$(curl -s "${TARGET_SERVER}/Observation?_summary=count" | jq '.total')
67+
68+
if [ "$ACTUAL_OBSERVATION_COUNT" -eq "$EXPECTED_OBSERVATION_COUNT" ]; then
69+
echo "✅ Observation count is correct: $ACTUAL_OBSERVATION_COUNT"
70+
else
71+
echo "❌ Observation count is $ACTUAL_OBSERVATION_COUNT, expected $EXPECTED_OBSERVATION_COUNT"
72+
exit 1
73+
fi
74+
75+
76+
echo "🎉 All integration tests passed!"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
events {}
2+
pid /tmp/nginx.pid;
3+
4+
http {
5+
server {
6+
listen 8080;
7+
server_name localhost;
8+
9+
location /fhir/ {
10+
proxy_pass http://torch:8080/fhir/;
11+
proxy_set_header Host $host;
12+
proxy_set_header X-Real-IP $remote_addr;
13+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
14+
proxy_set_header X-Forwarded-Proto $scheme;
15+
16+
proxy_http_version 1.1;
17+
proxy_set_header Upgrade $http_upgrade;
18+
proxy_set_header Connection "upgrade";
19+
20+
add_header X-Proxy-Target $proxy_host;
21+
add_header X-Upstream-Addr $upstream_addr;
22+
}
23+
24+
location / {
25+
root /app/output;
26+
index index.html;
27+
try_files $uri $uri/ =404;
28+
}
29+
30+
error_page 404 /404.html;
31+
location = /404.html {
32+
internal;
33+
}
34+
35+
access_log /var/log/nginx/access.log;
36+
error_log /var/log/nginx/error.log;
37+
}
38+
}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ dependencies
2020
package.json
2121
fhirpkg.lock.json
2222
.env
23-
exec-crtdl.sh
23+
exec-crtdl.sh

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,37 @@ For example a CRTDL only extracting Condition.onset will result in this:
270270
}
271271
```
272272

273+
## Transfer Script
274+
275+
TORCH provides a companion **transfer script** designed to automate the workflow of submitting a data extraction
276+
request, polling the status, and transferring the resulting files to a target FHIR server.
277+
278+
The transfer script will:
279+
280+
1. Take the **CRTDL** and generate a FHIR parameters resource to send to TORCH.
281+
2. Execute $extract-data operation on the TORCH server using parameters resource as input.
282+
3. Poll the TORCH status endpoint until the export is complete.
283+
4. Download the resulting patient-oriented FHIR bundles into a temp dir.
284+
5. Upload these files to a configured target FHIR server using the `blazectl` tool.
285+
6. Provide progress feedback and error handling at each step.
286+
287+
### Usage Example
288+
289+
```bash
290+
./transfer-extraction-to-dup-fhir-server.sh -c src/test/resources/CRTDL/CRTDL_observation.json -t http://target-fhir-server:8080/fhir
291+
```
292+
293+
### Environment Variables
294+
295+
The transfer script respects several environment variables to configure server URLs, directories, retry counts, and
296+
timing:
297+
298+
| Variable | Default | Description |
299+
|----------------|-----------------------|-----------------------------------------------------|
300+
| TORCH_BASE_URL | http://localhost:8080 | Base URL of the TORCH server |
301+
| MAX_RETRIES | 60 | Number of status polling attempts before timing out |
302+
| SLEEP_SECONDS | 5 | Seconds to wait between polling attempts |
303+
273304
## Supported Features
274305

275306
- Loading of StructureDefinitions

0 commit comments

Comments
 (0)