🐡
[Rust] SeaORMとSSH Jumperでssh tunnel越しにDBファーストなMariaDB(MySQL)開発
タイトルが下手
環境
開発環境
- 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