feat: create and delete sites on nginx with tests

This commit is contained in:
Aravinth Manivannan 2022-12-12 19:27:48 +05:30
parent 1e5a3d57b5
commit 62278abede
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
4 changed files with 186 additions and 6 deletions

49
env/nginx_bind_le/Cargo.lock generated vendored
View file

@ -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",

View file

@ -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"

View file

@ -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;
} }
} }

View file

@ -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()
}
} }