Skip to content

Commit

Permalink
Better nested services (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
akrantz01 authored May 16, 2022
1 parent ffcacb8 commit 3ca746f
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 46 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ itertools = '0.10'
once_cell = "1.8"
serde = { version = "1.0", features = ["derive"] }
serde_with = "1.9"
shrinkwraprs = "0.3"
tokio = { version = "1.6", features = ["fs", "macros", "process", "rt", "rt-multi-thread", "signal", "time"] }
tokio-stream = { version = "0.1", features = ["fs"] }

Expand Down
2 changes: 1 addition & 1 deletion docker/scripts/wafflemaker.hcl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Allow reading and writing to services configuration KV
path "services/data/+" {
path "services/data/*" {
capabilities = ["create", "update", "read"]
}

Expand Down
31 changes: 16 additions & 15 deletions src/management/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
registry::REGISTRY,
};
use serde::Serialize;
use warp::{http::StatusCode, Filter, Rejection, Reply};
use warp::{http::StatusCode, path::Tail, Filter, Rejection, Reply};

/// Build the routes for services
pub fn routes() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
Expand All @@ -15,18 +15,15 @@ pub fn routes() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clo
.with(named_trace("list"));

let get = warp::get()
.and(warp::path::param())
.and(warp::path::end())
.and(warp::path::tail())
.and_then(get)
.with(named_trace("get"));
let redeploy = warp::put()
.and(warp::path::param())
.and(warp::path::end())
.and(warp::path::tail())
.and_then(redeploy)
.with(named_trace("redeploy"));
let delete = warp::delete()
.and(warp::path::param())
.and(warp::path::end())
.and(warp::path::tail())
.and_then(delete)
.with(named_trace("delete"));

Expand Down Expand Up @@ -57,11 +54,13 @@ struct DependenciesResponse {
}

/// Get the configuration for a service
async fn get(service: String) -> Result<impl Reply, Rejection> {
async fn get(service: Tail) -> Result<impl Reply, Rejection> {
let service = service.as_str();

let reg = REGISTRY.read().await;
let cfg = reg.get(&service).ok_or_else(warp::reject::not_found)?;
let cfg = reg.get(service).ok_or_else(warp::reject::not_found)?;

let deployment_id = deployer::instance().service_id(&service).await?;
let deployment_id = deployer::instance().service_id(service).await?;

let dependencies = DependenciesResponse {
postgres: cfg.dependencies.postgres("").is_some(),
Expand Down Expand Up @@ -92,17 +91,19 @@ async fn get(service: String) -> Result<impl Reply, Rejection> {
}

/// Re-deploy a service
async fn redeploy(service: String) -> Result<impl Reply, Rejection> {
async fn redeploy(service: Tail) -> Result<impl Reply, Rejection> {
let service = service.as_str();

let reg = REGISTRY.read().await;
let config = reg.get(&service).ok_or_else(warp::reject::not_found)?;
let config = reg.get(service).ok_or_else(warp::reject::not_found)?;

jobs::dispatch(UpdateService::new(config.clone(), service));
jobs::dispatch(UpdateService::new(config.clone(), service.into()));

Ok(StatusCode::NO_CONTENT)
}

/// Delete a service
async fn delete(service: String) -> Result<impl Reply, Rejection> {
jobs::dispatch(DeleteService::new(service));
async fn delete(service: Tail) -> Result<impl Reply, Rejection> {
jobs::dispatch(DeleteService::new(service.as_str().into()));
Ok(StatusCode::NO_CONTENT)
}
16 changes: 6 additions & 10 deletions src/processor/jobs/delete_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ use super::Job;
use crate::{
deployer, dns, fail_notify,
notifier::{self, Event, State},
service::registry::REGISTRY,
service::{registry::REGISTRY, ServiceName},
vault,
};
use async_trait::async_trait;
use itertools::Itertools;
use tracing::{debug, info, instrument};

#[derive(Debug)]
pub struct DeleteService {
name: String,
name: ServiceName,
}

impl DeleteService {
/// Create a new delete service job
pub fn new(name: String) -> Self {
pub fn new(name: ServiceName) -> Self {
Self { name }
}
}
Expand All @@ -31,11 +30,8 @@ impl Job for DeleteService {
};
}

let sanitized_name = self.name.replace('/', ".");
let domain_name = self.name.split('/').rev().join(".");

let mut reg = REGISTRY.write().await;
if reg.remove(&self.name).is_none() {
if reg.remove(&self.name.proper).is_none() {
info!("service was never deployed, skipping");
notifier::notify(Event::service_delete(&self.name, State::Success)).await;
return;
Expand All @@ -59,10 +55,10 @@ impl Job for DeleteService {

fail!(vault::instance().revoke_leases(&id).await);

fail!(dns::instance().unregister(&domain_name).await);
fail!(dns::instance().unregister(&self.name.domain).await);

if vault::instance()
.delete_database_role(&sanitized_name)
.delete_database_role(&self.name.sanitized)
.await
.is_err()
{
Expand Down
20 changes: 8 additions & 12 deletions src/processor/jobs/update_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@ use crate::{
deployer::{self, CreateOpts},
dns, fail_notify,
notifier::{self, Event, State},
service::{registry::REGISTRY, AWSPart, Format, Secret, Service},
service::{registry::REGISTRY, AWSPart, Format, Secret, Service, ServiceName},
vault::{self, Aws},
};
use async_trait::async_trait;
use itertools::Itertools;
use rand::{distributions::Alphanumeric, Rng, RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use tracing::{debug, error, info, instrument, warn};

#[derive(Debug)]
pub struct UpdateService {
config: Service,
name: String,
name: ServiceName,
}

impl UpdateService {
/// Create a new update service job
pub fn new(config: Service, name: String) -> Self {
pub fn new(config: Service, name: ServiceName) -> Self {
Self { config, name }
}
}
Expand All @@ -39,9 +38,6 @@ impl Job for UpdateService {
let config = config::instance();
let service = &self.config;

let sanitized_name = self.name.replace('/', ".");
let domain_name = self.name.split('/').rev().join(".");

notifier::notify(Event::service_update(&self.name, State::InProgress)).await;

// Update the service in the registry
Expand All @@ -50,13 +46,13 @@ impl Job for UpdateService {

// Create the base container creation args
let mut options = CreateOpts::builder()
.name(&self.name)
.name(&*self.name)
.image(&service.docker.image, &service.docker.tag);

if service.web.enabled {
let domain = match service.web.domain.clone() {
Some(d) => d,
None => format!("{}.{}", &domain_name, &config.deployment.domain),
None => format!("{}.{}", &self.name.domain, &config.deployment.domain),
};

options = options.routing(domain, service.web.path.as_deref());
Expand Down Expand Up @@ -120,7 +116,7 @@ impl Job for UpdateService {
}
info!("loaded secrets from vault into environment");

if let Some(postgres) = service.dependencies.postgres(&sanitized_name) {
if let Some(postgres) = service.dependencies.postgres(&self.name.sanitized) {
// Create the role if it doesn't exist
let roles = fail!(vault::instance().list_database_roles().await);
if !roles.contains(&postgres.role.to_owned()) {
Expand Down Expand Up @@ -150,7 +146,7 @@ impl Job for UpdateService {
info!("loaded service dependencies into environment");

let known_services = fail!(deployer::instance().list().await);
let previous_id = known_services.get(&self.name);
let previous_id = known_services.get(&*self.name);

// Perform a rolling update of the service (if a previous version existed)
// Flow (assuming previous version existed):
Expand Down Expand Up @@ -204,7 +200,7 @@ impl Job for UpdateService {

// Register the internal DNS record(s)
let ip = fail!(deployer::instance().ip(&new_id).await);
fail!(dns::instance().register(&domain_name, &ip).await);
fail!(dns::instance().register(&self.name.domain, &ip).await);

info!("deployed with id \"{}\"", new_id);
notifier::notify(Event::service_update(&self.name, State::Success)).await;
Expand Down
12 changes: 9 additions & 3 deletions src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ use crate::config;
use globset::{Glob, GlobSet, GlobSetBuilder};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr, NoneAsEmptyString};
use std::fmt::Debug;
use std::{collections::HashMap, ffi::OsStr, path::Path};
use tokio::fs;

mod dependency;
mod name;
pub mod registry;
mod secret;

use dependency::*;
pub use name::ServiceName;
pub use secret::{Format, Part as AWSPart, Secret};

/// The configuration for a service
Expand All @@ -34,15 +37,18 @@ impl Service {
}

/// Generate the name of a service from its file path
pub fn name(path: &Path) -> String {
path.strip_prefix(&config::instance().git.clone_to)
pub fn name(path: &Path) -> ServiceName {
let name = path
.strip_prefix(&config::instance().git.clone_to)
.unwrap_or(path)
.with_extension("")
.iter()
.map(OsStr::to_str)
.map(Option::unwrap)
.collect::<Vec<_>>()
.join("/")
.join("/");

ServiceName::new(name)
}
}

Expand Down
65 changes: 65 additions & 0 deletions src/service/name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use itertools::Itertools;
use shrinkwraprs::Shrinkwrap;
use std::fmt::{Debug, Display, Formatter};

/// The different ways of referring to a service. Can be used as a standard string via [shrinkwraprs::Shrinkwrap]
#[derive(Shrinkwrap)]
pub struct ServiceName {
/// The proper name of the service by which most things should refer to it
#[shrinkwrap(main_field)]
pub proper: String,
/// The domain/subdomain name of the service
pub domain: String,
/// A sanitized version of the name containing only a-z, A-Z, 0-9, -, .
pub sanitized: String,
}

impl ServiceName {
pub(super) fn new<S: Into<String>>(name: S) -> ServiceName {
let proper = name.into();
let domain = proper.split('/').rev().join(".");
let sanitized = proper.replace('/', ".");

ServiceName {
proper,
domain,
sanitized,
}
}
}

impl AsRef<str> for ServiceName {
fn as_ref(&self) -> &str {
self.proper.as_ref()
}
}

impl Debug for ServiceName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.proper, f)
}
}

impl Display for ServiceName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.proper, f)
}
}

impl From<&str> for ServiceName {
fn from(name: &str) -> Self {
ServiceName::new(name)
}
}

impl From<String> for ServiceName {
fn from(name: String) -> Self {
ServiceName::new(name)
}
}

impl From<&String> for ServiceName {
fn from(name: &String) -> Self {
ServiceName::new(name)
}
}
2 changes: 1 addition & 1 deletion src/service/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn load_dir(reg: &mut HashMap<String, Service>, path: &Path) -> Result<()>
let service = Service::parse(&entry.path()).await?;

debug!("loaded service {}", &name);
reg.insert(name, service);
reg.insert(name.proper, service);
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/vault/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<'paths> Capabilities<'paths> {
m.insert("aws/creds/+", set![Read]);
m.insert("database/creds/+", set![Read]);
m.insert("database/roles/+", set![List, Create, Delete]);
m.insert("services/data/+", set![Create, Read, Update]);
m.insert("services/data/*", set![Create, Read, Update]);
m
}

Expand Down
2 changes: 1 addition & 1 deletion src/vault/renewal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub async fn leases(interval: Duration, max_percent: f64, mut stop: Receiver<()>
Err(e) => {
let reg = REGISTRY.read().await;
if let Some(config) = reg.get(service) {
jobs::dispatch(UpdateService::new(config.clone(), service.to_owned()));
jobs::dispatch(UpdateService::new(config.clone(), service.into()));
}

warn!(parent: &span, id = %lease.id, error = %e, "failed to renew lease");
Expand Down
Loading

0 comments on commit 3ca746f

Please sign in to comment.