feat: create and delete sites on nginx with tests
This commit is contained in:
parent
1e5a3d57b5
commit
62278abede
4 changed files with 186 additions and 6 deletions
49
env/nginx_bind_le/Cargo.lock
generated
vendored
49
env/nginx_bind_le/Cargo.lock
generated
vendored
|
@ -417,7 +417,10 @@ name = "nginx_bind_le"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"lazy_static",
|
||||||
"libconductor",
|
"libconductor",
|
||||||
|
"libconfig",
|
||||||
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tera",
|
"tera",
|
||||||
|
@ -634,6 +637,40 @@ version = "0.6.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "6.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "6.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "7.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
|
||||||
|
dependencies = [
|
||||||
|
"sha2",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -697,6 +734,17 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -801,6 +849,7 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
"memchr",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
|
6
env/nginx_bind_le/Cargo.toml
vendored
6
env/nginx_bind_le/Cargo.toml
vendored
|
@ -2,6 +2,7 @@
|
||||||
name = "nginx_bind_le"
|
name = "nginx_bind_le"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
include = ["/templates"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
@ -9,8 +10,11 @@ edition = "2021"
|
||||||
serde = { version = "1", features=["derive"]}
|
serde = { version = "1", features=["derive"]}
|
||||||
serde_json = { version ="1", features = ["raw_value"]}
|
serde_json = { version ="1", features = ["raw_value"]}
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
tokio = { version = "1.23.0", features = ["process"] }
|
tokio = { version = "1.23.0", features = ["process", "fs", "io-util"] }
|
||||||
tera = "1.17.1"
|
tera = "1.17.1"
|
||||||
|
rust-embed = "6.4.2"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
|
||||||
|
|
||||||
[dependencies.libconductor]
|
[dependencies.libconductor]
|
||||||
path = "../libconductor"
|
path = "../libconductor"
|
||||||
|
|
38
env/nginx_bind_le/src/lib.rs
vendored
38
env/nginx_bind_le/src/lib.rs
vendored
|
@ -20,6 +20,9 @@ use tokio::process::Command;
|
||||||
use libconductor::*;
|
use libconductor::*;
|
||||||
|
|
||||||
mod nginx;
|
mod nginx;
|
||||||
|
mod templates;
|
||||||
|
|
||||||
|
use nginx::Nginx;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct NginxBindLEConductor;
|
pub struct NginxBindLEConductor;
|
||||||
|
@ -33,10 +36,17 @@ impl Conductor for NginxBindLEConductor {
|
||||||
EventType::NewSite {
|
EventType::NewSite {
|
||||||
hostname,
|
hostname,
|
||||||
path,
|
path,
|
||||||
branch,
|
branch: _branch,
|
||||||
} => unimplemented!(),
|
} => {
|
||||||
EventType::Config { data } => unimplemented!(),
|
Nginx::new_site(&hostname, &path, None).await.unwrap();
|
||||||
EventType::DeleteSite { hostname } => unimplemented!(),
|
}
|
||||||
|
EventType::Config { hostname, data } => {
|
||||||
|
unimplemented!();
|
||||||
|
// Nginx::new_site(&hostname, &path, Some(data)).await.unwrap();
|
||||||
|
}
|
||||||
|
EventType::DeleteSite { hostname } => {
|
||||||
|
Nginx::rm_site(&hostname).await.unwrap();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
|
@ -44,7 +54,7 @@ impl Conductor for NginxBindLEConductor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn health(&self) -> bool {
|
async fn health(&self) -> bool {
|
||||||
nginx::Nginx::status().await
|
nginx::Nginx::env_exists() && nginx::Nginx::status().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -54,8 +64,26 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn all_good() {
|
async fn all_good() {
|
||||||
|
const HOSTNAME: &str = "lab.batsense.net";
|
||||||
let c = NginxBindLEConductor {};
|
let c = NginxBindLEConductor {};
|
||||||
assert_eq!(c.name(), CONDUCTOR_NAME);
|
assert_eq!(c.name(), CONDUCTOR_NAME);
|
||||||
assert!(c.health().await);
|
assert!(c.health().await);
|
||||||
|
if Nginx::site_exists(HOSTNAME) {
|
||||||
|
c.process(EventType::DeleteSite {
|
||||||
|
hostname: HOSTNAME.into(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
c.process(EventType::NewSite {
|
||||||
|
hostname: HOSTNAME.into(),
|
||||||
|
branch: "librepages".into(),
|
||||||
|
path: "/var/www/website/".into(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
c.process(EventType::DeleteSite {
|
||||||
|
hostname: HOSTNAME.into(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
99
env/nginx_bind_le/src/nginx.rs
vendored
99
env/nginx_bind_le/src/nginx.rs
vendored
|
@ -14,11 +14,30 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use tera::*;
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::templates::*;
|
||||||
|
|
||||||
pub struct Nginx;
|
pub struct Nginx;
|
||||||
|
|
||||||
impl Nginx {
|
impl Nginx {
|
||||||
|
pub async fn reload() -> MyResult<()> {
|
||||||
|
Command::new("sudo")
|
||||||
|
.arg("nginx")
|
||||||
|
.arg("-s")
|
||||||
|
.arg("reload")
|
||||||
|
.spawn()?
|
||||||
|
.wait()
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub async fn status() -> bool {
|
pub async fn status() -> bool {
|
||||||
async fn run_async_cmd(cmd: &mut Command) -> bool {
|
async fn run_async_cmd(cmd: &mut Command) -> bool {
|
||||||
if let Ok(mut child) = cmd.spawn() {
|
if let Ok(mut child) = cmd.spawn() {
|
||||||
|
@ -30,4 +49,84 @@ impl Nginx {
|
||||||
}
|
}
|
||||||
run_async_cmd(Command::new("sudo").arg("nginx").arg("-t")).await
|
run_async_cmd(Command::new("sudo").arg("nginx").arg("-t")).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn new_site(
|
||||||
|
hostname: &str,
|
||||||
|
path: &str,
|
||||||
|
config: Option<libconfig::Config>,
|
||||||
|
) -> MyResult<()> {
|
||||||
|
let config = CreateSite::new(hostname, path, config);
|
||||||
|
let contents = config.render();
|
||||||
|
let staging = Self::get_staging(hostname);
|
||||||
|
let prod = Self::get_prod(hostname);
|
||||||
|
|
||||||
|
let mut file = fs::File::create(&staging).await?;
|
||||||
|
file.write_all(contents.as_bytes()).await?;
|
||||||
|
file.sync_all().await?;
|
||||||
|
fs::symlink(&staging, &prod).await?;
|
||||||
|
Self::reload().await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_staging(hostname: &str) -> PathBuf {
|
||||||
|
Path::new(NGINX_STAGING_CONFIG_PATH).join(hostname)
|
||||||
|
}
|
||||||
|
fn get_prod(hostname: &str) -> PathBuf {
|
||||||
|
Path::new(NGINX_PRODUCTION_CONFIG_PATH).join(hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn site_exists(hostname: &str) -> bool {
|
||||||
|
Self::get_prod(hostname).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rm_site(hostname: &str) -> MyResult<()> {
|
||||||
|
let staging = Self::get_staging(hostname);
|
||||||
|
let prod = Self::get_prod(hostname);
|
||||||
|
|
||||||
|
fs::remove_file(&prod).await?;
|
||||||
|
fs::remove_file(&staging).await?;
|
||||||
|
Self::reload().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env_exists() -> bool {
|
||||||
|
let prod = Path::new(NGINX_PRODUCTION_CONFIG_PATH);
|
||||||
|
let staging = Path::new(NGINX_STAGING_CONFIG_PATH);
|
||||||
|
prod.exists() && prod.is_dir() && staging.exists() && staging.is_dir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateSite {
|
||||||
|
ctx: RefCell<Context>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CREATE_SITE: TemplateFile = TemplateFile::new("create_site", "nginx/create-site.j2");
|
||||||
|
pub const CREATE_SITE_FRAGMENT: TemplateFile =
|
||||||
|
TemplateFile::new("new_site_frag", "nginx/_new_site.fragement.j2");
|
||||||
|
|
||||||
|
pub const HOSTNAME_KEY: &str = "hostname";
|
||||||
|
pub const DOMAINS_KEY: &str = "domains";
|
||||||
|
pub const PATH_KEY: &str = "path";
|
||||||
|
pub const REDIRECTS_KEY: &str = "redirects";
|
||||||
|
|
||||||
|
pub const NGINX_STAGING_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-available/";
|
||||||
|
pub const NGINX_PRODUCTION_CONFIG_PATH: &str = "/etc/librepages/nginx/sites-enabled/";
|
||||||
|
|
||||||
|
type MyResult<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
impl CreateSite {
|
||||||
|
fn new(hostname: &str, path: &str, config: Option<libconfig::Config>) -> Self {
|
||||||
|
let ctx = RefCell::new(context());
|
||||||
|
ctx.borrow_mut().insert(HOSTNAME_KEY, hostname);
|
||||||
|
ctx.borrow_mut().insert(PATH_KEY, path);
|
||||||
|
if let Some(config) = config {
|
||||||
|
ctx.borrow_mut().insert(REDIRECTS_KEY, &config.redirects);
|
||||||
|
ctx.borrow_mut().insert(DOMAINS_KEY, &config.domains);
|
||||||
|
}
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> String {
|
||||||
|
TEMPLATES
|
||||||
|
.render(CREATE_SITE.name, &self.ctx.borrow())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue