Skip to content

Commit

Permalink
feat: added FHIR REST operation and only build a single container ima…
Browse files Browse the repository at this point in the history
…ge (#19)

* build(migrations): moved migration efbundle in same image as the API

* feat: added a first FHIR operations endpoint for creating pseudonyms
  • Loading branch information
chgl authored Oct 2, 2022
1 parent 1bdf077 commit 75f13c5
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 172 deletions.
File renamed without changes.
54 changes: 6 additions & 48 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ jobs:
outputs:
api-image-tags: ${{ steps.container_meta.outputs.tags }}
api-image-version: ${{ steps.container_meta.outputs.version }}
migrations-image-tags: ${{ steps.container_migrations_meta.outputs.tags }}
migrations-image-version: ${{ steps.container_migrations_meta.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
Expand All @@ -34,13 +32,6 @@ jobs:
images: |
ghcr.io/${{ github.repository }}
- name: Container meta for migrations image
id: container_migrations_meta
uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a # tag=v4
with:
images: |
ghcr.io/${{ github.repository }}-migrations
- name: Container meta for the unit test image
id: container_tests_meta
uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a # tag=v4
Expand Down Expand Up @@ -85,17 +76,6 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build and push migrations image
uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94 # tag=v3
with:
push: ${{ github.event_name != 'pull_request' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.container_migrations_meta.outputs.tags }}
labels: ${{ steps.container_migrations_meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
target: migrations

- name: Copy unit test coverage reports from container
env:
UNIT_TEST_IMAGE: ${{ fromJson(steps.container_tests_meta.outputs.json).tags[0] }}
Expand Down Expand Up @@ -127,18 +107,15 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
env:
API_IMAGE: ${{ fromJson(steps.container_meta.outputs.json).tags[0] }}
MIGRATIONS_IMAGE: ${{ fromJson(steps.container_migrations_meta.outputs.json).tags[0] }}
run: |
docker save "$API_IMAGE" -o /tmp/api-image.tar
docker save "$MIGRATIONS_IMAGE" -o /tmp/migrations-image.tar
- name: Upload container images
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0
with:
name: container-image-artifacts
path: |
/tmp/migrations-image.tar
/tmp/api-image.tar
test-api-container:
Expand Down Expand Up @@ -167,7 +144,6 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
run: |
ls -lar /tmp
docker load --input /tmp/migrations-image.tar
docker load --input /tmp/api-image.tar
docker image ls -a
Expand Down Expand Up @@ -247,8 +223,8 @@ jobs:
docker compose --profile=test -f docker-compose.yaml logs
docker compose --profile=test down --volumes --remove-orphans
test-migrations-container:
name: test migrations container
test-migrations:
name: test migrations
runs-on: ubuntu-22.04
permissions:
contents: read
Expand All @@ -268,7 +244,7 @@ jobs:
- name: Load migrations image
if: ${{ github.event_name == 'pull_request' }}
run: |
docker load --input /tmp/migrations-image.tar
docker load --input /tmp/api-image.tar
docker image ls -a
- name: Install .NET
Expand All @@ -279,7 +255,7 @@ jobs:

- name: Run migrations tests
env:
VFPS_MIGRATIONS_IMAGE_TAG: ${{ needs.build.outputs.migrations-image-version }}
VFPS_IMAGE_TAG: ${{ needs.build.outputs.api-image-version }}
run: dotnet test src/Vfps.IntegrationTests --configuration=Release -l "console;verbosity=detailed"

run-iter8-tests:
Expand Down Expand Up @@ -313,9 +289,6 @@ jobs:
# vfps api image
kind load image-archive /tmp/api-image.tar
# vfps migrations image
kind load image-archive /tmp/migrations-image.tar
- name: List images in cluster
run: docker exec kind-control-plane crictl images

Expand All @@ -326,7 +299,6 @@ jobs:
helm repo add chgl https://chgl.github.io/charts
helm install \
--set="image.tag=${IMAGE_TAG}" \
--set="migrationsJob.image.tag=${IMAGE_TAG}" \
-f tests/iter8/values.yaml \
--wait \
--timeout=10m \
Expand Down Expand Up @@ -393,7 +365,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' }}
needs:
- build
- test-migrations-container
- test-migrations
- test-api-container
permissions:
contents: read
Expand All @@ -410,7 +382,7 @@ jobs:
- name: Install Cosign
uses: sigstore/cosign-installer@f3c664df7af409cb4873aa5068053ba9d61a57b6 # tag=v2.6.0

- name: Sign api image
- name: Sign vfps image
env:
COSIGN_EXPERIMENTAL: "true"
IMAGES: ${{ needs.build.outputs.api-image-tags }}
Expand All @@ -423,17 +395,3 @@ jobs:
echo "Signing '$image' using keyless approach"
cosign sign "$image"
done <<< "$IMAGES"
- name: Sign migrations image
env:
COSIGN_EXPERIMENTAL: "true"
IMAGES: ${{ needs.build.outputs.migrations-image-tags }}
run: |
while read -r image; do
if [[ $image = quay.io/* ]]; then
echo "Skipping '$image' hosted on quay.io"
continue
fi
echo "Signing '$image' using keyless approach"
cosign sign "$image"
done <<< "$IMAGES"
49 changes: 17 additions & 32 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# syntax=docker/dockerfile:1.4
FROM mcr.microsoft.com/dotnet/nightly/aspnet:7.0.0-rc.1-jammy-chiseled@sha256:4011e4c1b5781ac8d48a322ee0b80f1742b8b5d1b50b8287e6c38ecaecd5575b AS runtime
WORKDIR /opt/vfps
EXPOSE 8080/tcp 8081/tcp
USER 65532:65532
ENV DOTNET_ENVIRONMENT="Production" \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
DOTNET_CLI_TELEMETRY_OPTOUT=1
USER 65532:65532
DOTNET_CLI_TELEMETRY_OPTOUT=1 \
ASPNETCORE_URLS="" \
DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp

FROM mcr.microsoft.com/dotnet/sdk:7.0.100-rc.1-jammy@sha256:5f968241760ea2469f029d769aec5c5f03ef55c608c63cfd74c7509757a3813a AS build
WORKDIR /build
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
PATH="/root/.dotnet/tools:${PATH}"

# TODO: remove "prerelease" once .NET 7 is officially released
RUN dotnet tool install --global dotnet-ef --prerelease
Expand All @@ -28,6 +33,14 @@ dotnet publish src/Vfps/Vfps.csproj \
--no-build \
--configuration=Release \
-o /build/publish

dotnet ef migrations bundle \
--project=src/Vfps/Vfps.csproj \
--startup-project=src/Vfps/Vfps.csproj \
--configuration=Release \
--runtime=linux-x64 \
--verbose \
-o /build/efbundle
EOF

FROM build AS unit-test
Expand All @@ -39,35 +52,7 @@ RUN dotnet test \
-l "console;verbosity=detailed" \
--settings=runsettings.xml

FROM build AS build-migrations
WORKDIR /build
ENV PATH="/root/.dotnet/tools:${PATH}"
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1

COPY --from=build /build .

RUN dotnet ef migrations bundle \
--project=src/Vfps/Vfps.csproj \
--startup-project=src/Vfps/Vfps.csproj \
--configuration=Release \
--runtime=linux-x64 \
--verbose \
-o /build/efbundle

FROM runtime AS migrations
WORKDIR /opt/vfps-database-migrations
ENV DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp

COPY --chown=65532:65532 --from=build-migrations /build/src/Vfps/appsettings.json .
COPY --chown=65532:65532 --from=build-migrations /build/efbundle .

ENTRYPOINT ["/opt/vfps-database-migrations/efbundle"]

FROM runtime
WORKDIR /opt/vfps
ENV ASPNETCORE_URLS=""
EXPOSE 8080/tcp 8081/tcp

COPY --chown=65532:65532 --from=build /build/publish .

COPY --chown=65532:65532 --from=build /build/efbundle .
ENTRYPOINT ["dotnet", "/opt/vfps/Vfps.dll"]
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,49 @@ See <https://github.com/chgl/charts/tree/master/charts/vfps> for a production-gr
The service exports metrics in Prometheus format on `/metrics`.
Health-, readiness-, and liveness-probes are exposed at `/healthz`, `/readyz`, and `/livez` respectively.

## FHIR operations

The service also exposes a FHIR operations endpoint. Sending a FHIR Parameters resource to `/v1/fhir/$create-pseudonym`
of the following schema:

```json
{
"resourceType": "Parameters",
"parameter": [
{
"name": "namespace",
"valueString": "test"
},
{
"name": "originalValue",
"valueString": "hello world"
}
]
}
```

will create a pseudonym in the `test` namespace. The expected response looks as follows:

```json
{
"resourceType": "Parameters",
"parameter": [
{
"name": "namespace",
"valueString": "test"
},
{
"name": "originalValue",
"valueString": "hello world"
},
{
"name": "pseudonymValue",
"valueString": "8KWwnm3TXR5R9iUDVVKD-jUezE4DEyeydOeq4v_a_b5ejSLmqOlT8g"
}
]
}
```

## Development

### Prerequisites
Expand Down Expand Up @@ -121,20 +164,12 @@ reportgenerator -reports:"./coverage/*/coverage.cobertura.xml" -targetdir:"cover
rm -rf coverage/
```

### Build container images

#### Main VFPS service
### Build container image

```sh
docker build -t ghcr.io/chgl/vfps:latest .
```

#### VFPS database migration container

```sh
docker build -t ghcr.io/chgl/vfps-migrations:latest --target=migrations .
```

## Benchmarks

### Micro benchmarks
Expand Down
Binary file modified docs/img/openapi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions iter8-output.md

This file was deleted.

35 changes: 16 additions & 19 deletions src/Vfps.IntegrationTests/MigrationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ public class MigrationsTests : IAsyncLifetime, IClassFixture<NetworkFixture>

private readonly TestcontainerDatabase postgresqlContainer;

private readonly NetworkFixture networkFixture;

private readonly string connectionString;

private readonly string migrationsImage;

private readonly ITestcontainersBuilder<TestcontainersContainer> migrationsContainerBuilder;

public MigrationsTests(ITestOutputHelper output, NetworkFixture networkFixture)
{
this.output = output;
this.networkFixture = networkFixture;

postgresqlContainer = new TestcontainersBuilder<PostgreSqlTestcontainer>()
.WithDatabase(new PostgreSqlTestcontainerConfiguration("docker.io/bitnami/postgresql:14.5.0-debian-11-r17")
Expand All @@ -38,23 +37,25 @@ public MigrationsTests(ITestOutputHelper output, NetworkFixture networkFixture)

this.connectionString = "Server=postgres;Port=5432;Database=vfps;User Id=postgres;Password=postgres;";

var migrationsImageTag = Environment.GetEnvironmentVariable("VFPS_MIGRATIONS_IMAGE_TAG") ?? "latest";
this.migrationsImage = $"ghcr.io/chgl/vfps-migrations:{migrationsImageTag}";
var migrationsImageTag = Environment.GetEnvironmentVariable("VFPS_IMAGE_TAG") ?? "latest";
this.migrationsImage = $"ghcr.io/chgl/vfps:{migrationsImageTag}";

migrationsContainerBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(migrationsImage)
.WithName("migrations")
.WithNetwork(networkFixture.Network.Id, networkFixture.Network.Name)
.WithEntrypoint("/opt/vfps/efbundle")
.WithCommand("--verbose", $"--connection={connectionString}");
}

[Fact]
public async Task RunMigrationsContainer_WithCorrectConnectionString_ShouldSucceed()
{
using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());

var migrationsContainerBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(migrationsImage)
.WithName("migrations")
await using var migrationsContainer = migrationsContainerBuilder
.WithOutputConsumer(consumer)
.WithNetwork(networkFixture.Network.Id, networkFixture.Network.Name)
.WithCommand("--verbose", $"--connection={connectionString}");

await using var migrationsContainer = migrationsContainerBuilder.Build();
.Build();

await migrationsContainer.StartAsync();

Expand All @@ -74,14 +75,10 @@ public async Task RunMigrationsContainer_WithWrongConnectionString_ShouldFail()
{
using var consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());

var migrationsContainerBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage(migrationsImage)
.WithName("migrations")
await using var migrationsContainer = migrationsContainerBuilder
.WithOutputConsumer(consumer)
.WithNetwork(networkFixture.Network.Id, networkFixture.Network.Name)
.WithCommand("--verbose", "--connection=Server=not-postgres;Port=5432;Database=vfps;User Id=postgres;Password=postgres;");

await using var migrationsContainer = migrationsContainerBuilder.Build();
.WithCommand("--verbose", "--connection=Server=not-postgres;Port=5432;Database=vfps;User Id=postgres;Password=postgres;")
.Build();

await migrationsContainer.StartAsync();

Expand Down
Loading

0 comments on commit 75f13c5

Please sign in to comment.