Nym-statistics-api : Postgres schema and SSL handling + Dockerfile and GitHub action (#5817)

* add option for ssl mode

* add dockerfile and dev util

* add github workflow for nym-statistics api

* apply review comments

* ci check for version + removed checks from push
This commit is contained in:
Simon Wicky
2025-06-03 12:06:00 +02:00
committed by GitHub
parent 3c6567ae64
commit adbe0392ca
9 changed files with 196 additions and 8 deletions
@@ -0,0 +1,57 @@
name: ci-check-nym-stats-api-version
on:
pull_request:
paths:
- "nym-statistics-api/**"
env:
WORKING_DIRECTORY: "nym-statistics-api"
jobs:
check-if-tag-exists:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Check if git tag exists
run: |
TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
if [[ -z "$TAG" ]]; then
echo "Tag is empty"
exit 1
fi
git ls-remote --tags origin | awk '{print $2}'
if git ls-remote --tags origin | awk '{print $2}' | grep -q "refs/tags/$TAG$" ; then
echo "Tag '$TAG' ALREADY EXISTS on the remote"
exit 1
else
echo "Tag '$TAG' does not exist on the remote"
fi
- name: Check if harbor tag exists
run: |
TAG=${{ steps.get_version.outputs.result }}
registry=https://harbor.nymte.ch
repo_name=nym/nym-statistics-api
if [[ -z $TAG ]]; then
echo "Tag is empty"
exit 1
fi
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
if [[ $exists = "true" ]]; then
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
exit 1
elif [[ $exists = "false" ]]; then
echo "Version '$TAG' doesn't exist on the remote"
else
echo "Unknown output '$exists'"
exit 1
fi
@@ -0,0 +1,42 @@
name: Build and upload Nym Statistics API container to harbor.nymte.ch
on:
workflow_dispatch:
env:
WORKING_DIRECTORY: "nym-statistics-api"
CONTAINER_NAME: "nym-statistics-api"
jobs:
build-container:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.nymte.ch
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
- name: Checkout repo
uses: actions/checkout@v4
- name: Configure git identity
run: |
git config --global user.email "lawrence@nymtech.net"
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Create tag
run: |
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
- name: BuildAndPushImageOnHarbor
run: |
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+2
View File
@@ -0,0 +1,2 @@
*.pem
.env*
+34
View File
@@ -0,0 +1,34 @@
# this will only work with VPN, otherwise remove the harbor part
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
COPY ./ /usr/src/nym
WORKDIR /usr/src/nym/nym-statistics-api
RUN cargo build --release
#-------------------------------------------------------------------
# The following environment variables are required at runtime:
#
# DATABASE_URL
# POSTGRES_USER
# POSTGRES_PASSWORD
# PG_SSL_CERT
#
# And optionally:
#
# NYM_API_URL
# NYM_STATISTICS_API_HTTP_PORT
# PGPORT
#
# see https://github.com/nymtech/nym/blob/develop/nym-statistics-api/src/cli/mod.rs for details
#-------------------------------------------------------------------
FROM harbor.nymte.ch/dockerhub/ubuntu:24.04
RUN apt-get update && apt-get install -y ca-certificates
WORKDIR /nym
COPY --from=builder /usr/src/nym/target/release/nym-statistics-api ./
ENTRYPOINT [ "/nym/nym-statistics-api" ]
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -e
user_rust_log_preference=$RUST_LOG
function run_bare() {
# export necessary env vars
set -a
source .env
set +a
export RUST_LOG=${user_rust_log_preference:-debug}
echo "RUST_LOG=${RUST_LOG}"
cargo run --package nym-statistics-api
}
# Requires pg_up.sh, or a running postres instance, with the correct parameters in an .env file
run_bare
+7
View File
@@ -16,15 +16,22 @@ DB_NAME=$DB_NAME
DATABASE_URL=$DATABASE_URL
EOF
cat <<EOF > init_schema.sql
CREATE SCHEMA private_statistics_api;
EOF
docker run --rm -it \
--name ${DB_NAME} \
-e POSTGRES_USER=${PGUSER} \
-e POSTGRES_PASSWORD=${PGPASSWORD} \
-e POSTGRES_DB=${DB_NAME} \
-v $(pwd)/init_schema.sql:/docker-entrypoint-initdb.d/init_schema.sql \
-p ${PGPORT}:${PGPORT} \
postgres
rm init_schema.sql
# sqlx migrate run
# cargo sqlx prepare
+5 -1
View File
@@ -1,6 +1,6 @@
use clap::Parser;
use nym_bin_common::bin_info;
use std::sync::OnceLock;
use std::{path::PathBuf, sync::OnceLock};
use url::Url;
// Helper for passing LONG_VERSION to clap
@@ -35,4 +35,8 @@ pub(crate) struct Cli {
/// PgSQL port for the database.
#[clap(long, default_value_t = 5432, env = "PGPORT")]
pub(crate) pg_port: u16,
/// SSL root certificate path. Providing this will also set the ssl mode to `Require`
#[clap(long, env = "PG_SSL_CERT")]
pub(crate) ssl_cert_path: Option<PathBuf>,
}
+1
View File
@@ -22,6 +22,7 @@ async fn main() -> anyhow::Result<()> {
args.username,
args.password,
args.pg_port,
args.ssl_cert_path,
)
.await?;
tracing::info!("Connection to database successful");
+27 -7
View File
@@ -1,12 +1,18 @@
use anyhow::{anyhow, Result};
use models::{ConnectionInfoDto, DailyActiveDeviceDto};
use sqlx::{migrate::Migrator, postgres::PgConnectOptions};
use std::str::FromStr;
use sqlx::{
migrate::Migrator,
postgres::{PgConnectOptions, PgPoolOptions},
Executor,
};
use std::{path::PathBuf, str::FromStr};
pub(crate) mod models;
pub(crate) type DbPool = sqlx::PgPool;
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
const SET_SEARCH_PATH: &str = "SET search_path = private_statistics_api";
#[derive(Debug, Clone)]
pub(crate) struct StatisticsStorage {
@@ -19,14 +25,31 @@ impl StatisticsStorage {
user: String,
password: String,
port: u16,
ssl_cert_path: Option<PathBuf>,
) -> Result<Self> {
let connect_options = PgConnectOptions::from_str(&connection_url)?
let mut connect_options = PgConnectOptions::from_str(&connection_url)?
.port(port)
.username(&user)
.password(&password)
.application_name(nym_bin_common::bin_info!().binary_name);
let pool = sqlx::PgPool::connect_with(connect_options)
if let Some(ssl_cert) = ssl_cert_path {
connect_options = connect_options
.ssl_mode(sqlx::postgres::PgSslMode::Require)
.ssl_root_cert(ssl_cert);
}
// This is a custom connection so the _sqlx_migrations table is not written in the public schema
// It then ensures we'll only write in the given schema, allowing to have the schema name only once here
// Ref : https://github.com/launchbadge/sqlx/issues/1835
let pool = PgPoolOptions::new()
.after_connect(|conn, _meta| {
Box::pin(async move {
conn.execute(SET_SEARCH_PATH).await?;
Ok(())
})
})
.connect_with(connect_options)
.await
.map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?;
@@ -49,9 +72,6 @@ impl StatisticsStorage {
Ok(())
}
// Interestingly enough, because gateway-storage is using the `chrono` feature of sqlx and in 0.7.4 it takes priority over the `time` one, we cannot use the query! macro here.
// Due to features unification, the binary will not compile when built from the workspace root because it will expect `chrono` types.
// As a consequence, there is no compile time verification of these queries.
async fn store_device(&self, active_device: DailyActiveDeviceDto) -> Result<()> {
sqlx::query!(
r#"INSERT INTO active_device (