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:
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
*.pem
|
||||
.env*
|
||||
@@ -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" ]
|
||||
Executable
+21
@@ -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
|
||||
@@ -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
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user