🐡

[Rust] SeaORMとSSH Jumperでssh tunnel越しにDBファーストなMariaDB(MySQL)開発

2022/10/27に公開

タイトルが下手

環境

開発環境

  • cargo 1.64.0 (387270bc7 2022-09-16)
  • rustc 1.64.0 (a55dd71d5 2022-09-19)
  • WSL Debian 5.15.57.1-microsoft-standard-WSL2

データベース環境

  • MariaDB 10.9.3
  • Debian 5.10.140-1 (2022-09-02)

実装

プロジェクト構造

./
├── .env
├── Cargo.lock
├── Cargo.toml
├── migrations
└── src
    ├── lib.rs
    ├── main.rs
    └── models
        └── SeaORMのEntityたち

Dependency

Cargo.toml
[dependencies]
dotenvy = "0.15"
sea-orm =  { version = "0.10.0", features = [ "sqlx-mysql", "runtime-tokio-rustls", "macros" ] }
ssh_jumper = "0.4.0"
tokio = { version = "1.21.2", features = ["full"] }

tokioはfullじゃなくてもいいはず

Database First

先にデータベースを作っておいてあとからSeaORMのEntityを作成する
公式の通りにやるだけ
sea-orm-cliを使うのでインストール

cargo install sea-orm-cli

(もしかしたらこのときにdefault-libmysqlclient-devやらlibmariadb-devが必要になるかもしれない)

sea-orm-cli用にssh tunnelを張っておく

ssh -L 3306:localhost:3306 <username>@<database_host>

./src/models/にEntityを作成

sea-orm-cli generate entity -u mysql://<username>:<password>@localhost/<database_name> -o ./src/models

ssh tunnelはもう要らないので消しておく

./src/lib.rsにmodelsを公開するように書く

lib.rs
pub mod models;

.envに接続先を設定

.env
DATABASE_URL=mysql://<username>:<password>@localhost/<database_name>
USERNAME=<ssh_username>
PASSWORD=<ssh_password>
DATABASE_HOST_IP=<database_ip_address>

SSH Jumperでssh tunnelを張る

ssh tunnelを張るのにSSH Jumperを使う

main.rs
use ssh_jumper::{
    model::{AuthMethod, HostAddress, HostSocketParams, JumpHostAuthParams, SshTunnelParams},
    SshJumper,
};
use std::net::IpAddr;
use std::{borrow::Cow, str::FromStr};

use dotenvy::dotenv;
use std::env;

#[tokio::main]
async fn main() {
    dotenv().ok();
    let username = env::var("USERNAME").unwrap();
    let password = env::var("PASSWORD").unwrap();
    let database_host_ip_string = env::var("DATABASE_HOST_IP").unwrap();
    let (local_socket_addr, ssh_forwarder_end_rx) = {
       let jump_host = HostAddress::IpAddr(IpAddr::from_str(&database_host_ip_string).unwrap());
        let jump_host_auth_params = JumpHostAuthParams {
            user_name: Cow::Borrowed(&username),
            auth_method: AuthMethod::Password {
                password: Cow::Borrowed(&password),
            },
        };
        let target_socket = HostSocketParams {
            address: HostAddress::HostName(Cow::Borrowed("localhost")),
            port: 3306,
        };
        let ssh_params = SshTunnelParams::new(jump_host, jump_host_auth_params, target_socket)
            .with_local_port(3306);
        SshJumper::open_tunnel(&ssh_params).await.unwrap()
    };
    assert_eq!("127.0.0.1:3306".to_string(), local_socket_addr.to_string());
}

エラーが出なければOK

SeaORMでDBにアクセス

SeaORMでDBにアクセスする

main.rs
use <your_project_name>::models::prelude::*;
use sea_orm::Database;
use sea_orm::EntityTrait;
//~中略~
let db_uri = env::var("DATABASE_URL").unwrap();
let db = Database::connect(db_uri).await.unwrap();
let rows = Hoge::find().all(&db).await.unwrap();
println!("{:?}", rows);

Hogeの部分は先に作っておいたEntityの名前に合わせる
エラーが出ずにレコードが表示されればOK

今回書いたmain.rs

main.rs
use ssh_jumper::{
    model::{AuthMethod, HostAddress, HostSocketParams, JumpHostAuthParams, SshTunnelParams},
    SshJumper,
};
use std::net::IpAddr;
use std::{borrow::Cow, str::FromStr};

use dotenvy::dotenv;
use std::env;

use <your_project_name>::models::prelude::*;

use sea_orm::Database;
use sea_orm::EntityTrait;

#[tokio::main]
async fn main() {
    dotenv().ok();
    let username = env::var("USERNAME").unwrap();
    let password = env::var("PASSWORD").unwrap();
    let database_host_ip_string = env::var("DATABASE_HOST_IP").unwrap();
    let (local_socket_addr, ssh_forwarder_end_rx) = {
        let jump_host = HostAddress::IpAddr(IpAddr::from_str(&database_host_ip_string).unwrap());
        let jump_host_auth_params = JumpHostAuthParams {
            user_name: Cow::Borrowed(&username),
            auth_method: AuthMethod::Password {
                password: Cow::Borrowed(&password),
            },
        };
        let target_socket = HostSocketParams {
            address: HostAddress::HostName(Cow::Borrowed("localhost")),
            port: 3306,
        };
        let ssh_params = SshTunnelParams::new(jump_host, jump_host_auth_params, target_socket)
            .with_local_port(3306);
        SshJumper::open_tunnel(&ssh_params).await.unwrap()
    };
    assert_eq!("127.0.0.1:3306".to_string(), local_socket_addr.to_string());
    let db_uri = env::var("DATABASE_URL").unwrap();
    let db = Database::connect(db_uri).await.unwrap();
    let rows = Hoge::find().all(&db).await.unwrap();
    println!("{:?}", rows);
}

追記

DB側のBIT型が何故かStringでEntity生成されてしまうのでboolにしてあげると型が通る

Discussion