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"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"lazy_static",
|
||||
"libconductor",
|
||||
"libconfig",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tera",
|
||||
|
@ -634,6 +637,40 @@ version = "0.6.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
|
@ -697,6 +734,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -801,6 +849,7 @@ dependencies = [
|
|||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"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"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
include = ["/templates"]
|
||||
|
||||
# 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_json = { version ="1", features = ["raw_value"]}
|
||||
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"
|
||||
rust-embed = "6.4.2"
|
||||
lazy_static = "1.4.0"
|
||||
libconfig = { version = "0.1.0", git = "https://git.batsense.net/librepages/libconfig" }
|
||||
|
||||
[dependencies.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::*;
|
||||
|
||||
mod nginx;
|
||||
mod templates;
|
||||
|
||||
use nginx::Nginx;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NginxBindLEConductor;
|
||||
|
@ -33,10 +36,17 @@ impl Conductor for NginxBindLEConductor {
|
|||
EventType::NewSite {
|
||||
hostname,
|
||||
path,
|
||||
branch,
|
||||
} => unimplemented!(),
|
||||
EventType::Config { data } => unimplemented!(),
|
||||
EventType::DeleteSite { hostname } => unimplemented!(),
|
||||
branch: _branch,
|
||||
} => {
|
||||
Nginx::new_site(&hostname, &path, None).await.unwrap();
|
||||
}
|
||||
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 {
|
||||
|
@ -44,7 +54,7 @@ impl Conductor for NginxBindLEConductor {
|
|||
}
|
||||
|
||||
async fn health(&self) -> bool {
|
||||
nginx::Nginx::status().await
|
||||
nginx::Nginx::env_exists() && nginx::Nginx::status().await
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
@ -54,8 +64,26 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn all_good() {
|
||||
const HOSTNAME: &str = "lab.batsense.net";
|
||||
let c = NginxBindLEConductor {};
|
||||
assert_eq!(c.name(), CONDUCTOR_NAME);
|
||||
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
|
||||
* 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 crate::templates::*;
|
||||
|
||||
pub struct 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 {
|
||||
async fn run_async_cmd(cmd: &mut Command) -> bool {
|
||||
if let Ok(mut child) = cmd.spawn() {
|
||||
|
@ -30,4 +49,84 @@ impl Nginx {
|
|||
}
|
||||
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