diff --git a/Cargo.lock b/Cargo.lock index dd64b9b..a2e33c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,50 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.72", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "async-lock" version = "3.4.0" @@ -121,13 +165,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 1.0.106", + "syn 2.0.72", ] [[package]] @@ -154,6 +198,61 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -187,6 +286,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -548,6 +656,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "dhcproto" version = "0.12.0" @@ -1145,12 +1263,101 @@ dependencies = [ "windows", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1366,6 +1573,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -1403,6 +1616,22 @@ dependencies = [ "quote", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1514,6 +1743,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -1646,7 +1881,6 @@ dependencies = [ "moka", "nzr-api", "serde", - "tarpc", "tokio", "tracing", "tracing-subscriber", @@ -1661,6 +1895,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "omyacid" +version = "0.1.0" +dependencies = [ + "anyhow", + "askama", + "axum", + "moka", + "nzr-api", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1865,6 +2113,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2098,6 +2352,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.12" @@ -2162,6 +2422,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.7" @@ -2285,9 +2555,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] @@ -2591,6 +2861,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "syslog" version = "7.0.0" @@ -2720,13 +3002,16 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", "libc", + "num-conv", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -2734,16 +3019,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -2877,6 +3163,34 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -3005,6 +3319,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index cf28bd4..43bb737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["nzrd", "nzr-api", "client", "nzrdhcp", "nzr-virt"] +members = ["nzrd", "nzr-api", "client", "nzrdhcp", "nzr-virt", "omyacid"] resolver = "2" diff --git a/nzr-api/src/config.rs b/nzr-api/src/config.rs index 1eade15..1e23b9e 100644 --- a/nzr-api/src/config.rs +++ b/nzr-api/src/config.rs @@ -32,6 +32,7 @@ pub struct SOAConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DNSConfig { pub listen_addr: String, + pub port: u16, pub default_zone: Name, pub soa: SOAConfig, } @@ -40,6 +41,15 @@ pub struct DNSConfig { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DHCPConfig { pub listen_addr: String, + pub port: u16, +} + +/// Cloud-init configuration, used by omyacid. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CloudConfig { + pub listen_addr: String, + pub port: u16, + pub admin_user: String, } /// Server<->Client RPC configuration. @@ -62,6 +72,7 @@ pub struct Config { pub storage: StorageConfig, pub dns: DNSConfig, pub dhcp: DHCPConfig, + pub cloud: CloudConfig, } impl Default for Config { @@ -84,8 +95,9 @@ impl Default for Config { base_image_pool: "images".to_owned(), }, dns: DNSConfig { - listen_addr: "127.0.0.1:5353".to_owned(), - default_zone: Name::from_utf8("servers.locaddral").unwrap(), + listen_addr: "127.0.0.1".to_owned(), + port: 5353, + default_zone: Name::from_utf8("servers.local").unwrap(), soa: SOAConfig { nzr_domain: Name::from_utf8("nzr.local").unwrap(), contact: Name::from_utf8("admin.nzr.local").unwrap(), @@ -96,6 +108,12 @@ impl Default for Config { }, dhcp: DHCPConfig { listen_addr: "127.0.0.1".to_owned(), + port: 67, + }, + cloud: CloudConfig { + listen_addr: "0.0.0.0".to_owned(), + port: 80, + admin_user: "admin".to_owned(), }, } } diff --git a/omyacid/Cargo.toml b/omyacid/Cargo.toml new file mode 100644 index 0000000..cc52e8d --- /dev/null +++ b/omyacid/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "omyacid" +version = "0.1.0" +edition = "2021" + +[dependencies] +nzr-api = { path = "../nzr-api" } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +axum = "0.7" +tracing = "0.1" +tracing-subscriber = "0.3" +anyhow = "1" +askama = "0.12" +moka = { version = "0.12.8", features = ["future"] } diff --git a/omyacid/src/ctx.rs b/omyacid/src/ctx.rs new file mode 100644 index 0000000..b894da5 --- /dev/null +++ b/omyacid/src/ctx.rs @@ -0,0 +1,95 @@ +use std::hash::RandomState; +use std::net::Ipv4Addr; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Context as _; +use anyhow::Result; +use moka::future::Cache; +use nzr_api::config::Config; +use nzr_api::model::Instance; +use nzr_api::InstanceQuery; +use nzr_api::NazrinClient; +use tokio::net::UnixStream; + +#[derive(Clone)] +struct InstanceMeta { + pub inst: Instance, + pub userdata: Vec, +} + +#[derive(Clone)] +pub struct Context { + api_client: NazrinClient, + config: Arc, + host_cache: Cache, +} + +impl Context { + pub async fn new(cfg: Config) -> Result { + let api_client = { + let sock = UnixStream::connect(&cfg.rpc.socket_path) + .await + .context("Connection to nzrd failed")?; + nzr_api::new_client(sock) + }; + + let host_cache = Cache::builder() + .time_to_live(Duration::from_secs(15)) + .max_capacity(5) + .build(); + + Ok(Self { + api_client, + host_cache, + config: Arc::new(cfg), + }) + } + + // Internal function to hydrate the instance metadata, if needed + async fn get_instmeta(&self, addr: Ipv4Addr) -> Result> { + if let Some(meta) = self.host_cache.get(&addr).await { + tracing::debug!("Cache hit!"); + Ok(Some(meta)) + } else { + let inst = self + .api_client + .find_instance(nzr_api::default_ctx(), InstanceQuery::Ipv4Addr(addr)) + .await + .context("RPC error")? + .map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?; + if let Some(inst) = inst { + let userdata = self + .api_client + .get_instance_userdata(nzr_api::default_ctx(), inst.id) + .await + .context("RPC error")? + .map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?; + + let meta = InstanceMeta { inst, userdata }; + + self.host_cache.insert(addr, meta.clone()).await; + + Ok(Some(meta)) + } else { + Ok(None) + } + } + } + + pub async fn get_instance(&self, addr: Ipv4Addr) -> Result> { + self.get_instmeta(addr) + .await + .map(|opt| opt.map(|im| im.inst)) + } + + pub async fn get_inst_userdata(&self, addr: Ipv4Addr) -> Result>> { + self.get_instmeta(addr) + .await + .map(|opt| opt.map(|im| im.userdata)) + } + + pub fn cfg(&self) -> &Config { + &self.config + } +} diff --git a/omyacid/src/main.rs b/omyacid/src/main.rs new file mode 100644 index 0000000..0090558 --- /dev/null +++ b/omyacid/src/main.rs @@ -0,0 +1,120 @@ +mod ctx; +mod model; + +use std::{ + net::{IpAddr, SocketAddr}, + process::ExitCode, + str::FromStr, +}; + +use askama::Template; +use axum::{ + extract::{ConnectInfo, State}, + http::StatusCode, + routing::get, + Router, +}; +use model::Metadata; +use nzr_api::config::Config; + +async fn get_meta_data( + State(ctx): State, + ConnectInfo(addr): ConnectInfo, +) -> Result { + if let IpAddr::V4(ip) = addr.ip() { + match ctx.get_instance(ip).await { + Ok(Some(inst)) => { + let meta = Metadata { + inst_name: &inst.name, + ssh_pubkeys: Vec::new(), // TODO + username: Some(ctx.cfg().cloud.admin_user.as_ref()), + }; + + meta.render().map_err(|e| { + tracing::error!("Renderer error: {e}"); + StatusCode::INTERNAL_SERVER_ERROR + }) + } + Ok(None) => { + tracing::warn!("Request from unregistered server {ip}"); + Err(StatusCode::FORBIDDEN) + } + Err(err) => { + tracing::warn!("{err}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } + } else { + Err(StatusCode::BAD_REQUEST) + } +} + +async fn get_user_data( + State(ctx): State, + ConnectInfo(addr): ConnectInfo, +) -> Result, StatusCode> { + if let IpAddr::V4(ip) = addr.ip() { + match ctx.get_inst_userdata(ip).await { + Ok(Some(data)) => Ok(data), + Ok(None) => { + tracing::warn!("Request from unregistered server {ip}"); + Err(StatusCode::FORBIDDEN) + } + Err(err) => { + tracing::warn!("{err}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } + } else { + Err(StatusCode::BAD_REQUEST) + } +} + +#[tokio::main] +async fn main() -> ExitCode { + tracing_subscriber::fmt().init(); + let cfg: Config = match Config::figment().extract() { + Ok(cfg) => cfg, + Err(err) => { + tracing::error!("Unable to get configuration: {err}"); + return ExitCode::FAILURE; + } + }; + + let http_sock = { + let addr = match IpAddr::from_str(&cfg.cloud.listen_addr) { + Ok(addr) => addr, + Err(err) => { + tracing::error!("Invalid listen IP address ({err})"); + return ExitCode::FAILURE; + } + }; + match tokio::net::TcpListener::bind((addr, cfg.cloud.port)).await { + Ok(sock) => sock, + Err(err) => { + tracing::error!("Failed to bind to {addr}:{}: {err}", cfg.cloud.port); + return ExitCode::FAILURE; + } + } + }; + + let ctx = match ctx::Context::new(cfg).await { + Ok(ctx) => ctx, + Err(err) => { + tracing::error!("{err}"); + return ExitCode::FAILURE; + } + }; + + let app = Router::new() + .route("/meta-data", get(get_meta_data)) + .route("/user-data", get(get_user_data)) + .with_state(ctx); + + if let Err(err) = axum::serve(http_sock, app).await { + tracing::error!("axum error: {err}"); + return ExitCode::FAILURE; + } + + ExitCode::SUCCESS +} diff --git a/omyacid/src/model.rs b/omyacid/src/model.rs new file mode 100644 index 0000000..b2d0e25 --- /dev/null +++ b/omyacid/src/model.rs @@ -0,0 +1,8 @@ +use askama::Template; +#[derive(Template)] +#[template(path = "meta-data.yml")] +pub struct Metadata<'a> { + pub inst_name: &'a str, + pub ssh_pubkeys: Vec<&'a String>, + pub username: Option<&'a str>, +} diff --git a/omyacid/templates/meta-data.yml b/omyacid/templates/meta-data.yml new file mode 100644 index 0000000..8c0d504 --- /dev/null +++ b/omyacid/templates/meta-data.yml @@ -0,0 +1,11 @@ +instance_id: "iid-{{ inst_name }}" +local_hostname: "{{ inst_name }}" +{% if !ssh_pubkeys.is_empty() -%} +public_keys: +{% for key in ssh_pubkeys -%} +- "{{ key }}" +{% endfor %} +{% endif -%} +{% if let Some(user) = username -%} +default_username: "{{ user }}" +{%- endif %} \ No newline at end of file