Skip to content

Commit

Permalink
Merge branch 'version-bans'
Browse files Browse the repository at this point in the history
  • Loading branch information
ackwell committed Oct 8, 2024
2 parents 16b0bdb + 81d7568 commit cb6f45d
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 27 deletions.
41 changes: 34 additions & 7 deletions src/http/admin/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,25 @@ pub fn router(state: HttpState) -> Router {
async fn get_version(
OriginalUri(uri): OriginalUri,
Path(version_key): Path<VersionKey>,
State(Service { version, .. }): State<Service>,
State(Service {
version: version_service,
..
}): State<Service>,
) -> Result<impl IntoResponse> {
let names = version.names(version_key).context("unknown version")?;
let version = version_service
.version(version_key)
.context("unknown version")?;

let banned = version.ban_time.is_some();

let names = version_service
.names(version_key)
.context("unknown version")?;

// Patches are stored in oldest-first order for IW, which is lovely in code
// and horrible for reading. Given this is ostensibly the reading bit of the
// application, fix that.
let patch_list = version
.version(version_key)
.context("unknown version")?
.repositories
.into_iter()
.map(|repository| {
Expand All @@ -59,6 +68,17 @@ async fn get_version(
Ok((BaseTemplate {
title: format!("version {}", version_key),
content: html! {
h2 { "status" }
form action=(uri) method="post" {
ul {
li {
@if banned { "banned" }
@else { "ready" }
button type="submit" name="ban" value=(!banned) { "toggle ban" }
}
}
}

h2 { "names" }
form action=(uri) method="post" {
input type="text" name="names" value={
Expand Down Expand Up @@ -94,7 +114,8 @@ async fn get_version(

#[derive(Debug, Deserialize)]
struct VersionPostRequest {
names: String,
names: Option<String>,
ban: Option<bool>,
}

#[debug_handler(state = HttpState)]
Expand All @@ -104,8 +125,14 @@ async fn post_version(
State(Service { version, .. }): State<Service>,
Form(request): Form<VersionPostRequest>,
) -> Result<impl IntoResponse> {
let names = request.names.split(',').map(str::trim);
version.set_names(version_key, names).await?;
if let Some(names) = request.names {
let names = names.split(',').map(str::trim);
version.set_names(version_key, names).await?;
}

if let Some(ban) = request.ban {
version.set_banned(version_key, ban).await?;
}

Ok(Redirect::to(&uri.to_string()))
}
Expand Down
19 changes: 14 additions & 5 deletions src/http/admin/versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ struct VersionInfo {
key: VersionKey,
patches: Vec<(String, String)>,
names: Vec<String>,
banned: bool,
}

#[debug_handler(state = HttpState)]
async fn versions(
OriginalUri(uri): OriginalUri,
State(Service { version, .. }): State<Service>,
State(Service {
version: version_service,
..
}): State<Service>,
) -> Result<impl IntoResponse> {
let version_info = |key: VersionKey| -> Result<_> {
let version = version_service.version(key).context("missing version")?;

let latest = version
.version(key)
.context("missing version")?
.repositories
.into_iter()
.map(|repository| (repository.name, repository.patches.last().name.clone()))
Expand All @@ -42,11 +46,12 @@ async fn versions(
Ok(VersionInfo {
key,
patches: latest,
names: version.names(key).context("missing version")?,
names: version_service.names(key).context("missing version")?,
banned: version.ban_time.is_some(),
})
};

let versions = version
let versions = version_service
.keys()
.into_iter()
.map(version_info)
Expand All @@ -67,6 +72,10 @@ async fn versions(
(name)
}
")"

@if version.banned {
" (banned)"
}
}

dl {
Expand Down
40 changes: 33 additions & 7 deletions src/version/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
io::{self, Read},
path::{Path, PathBuf},
sync::RwLock,
time::SystemTime,
};

use anyhow::Result;
Expand Down Expand Up @@ -143,6 +144,23 @@ impl Manager {
Some(names)
}

/// Set whether the specified version is banned. Banned versions will be
/// omitted from version manager behavior until unbanned.
pub async fn set_banned(&self, key: VersionKey, banned: bool) -> Result<()> {
let version_clone = {
let mut versions = self.versions.write().expect("poisoned");
let Some(version) = versions.get_mut(&key) else {
anyhow::bail!("unknown version {key}");
};

version.ban_time = banned.then(SystemTime::now);
version.clone()
};
self.persist_version(key, version_clone).await?;

Ok(())
}

/// Set the names for the specified version. If a name already exists, it
/// will be updated to match.
pub async fn set_names(
Expand Down Expand Up @@ -201,7 +219,10 @@ impl Manager {
let repositories = try_join_all(pending_repositories).await?;

// Build a version struct and it's associated key and save it to the versions map.
let version = Version { repositories };
let mut version = Version {
repositories,
ban_time: None,
};
let key = VersionKey::from(&version);

let mut versions = self.versions.write().expect("poisoned");
Expand All @@ -215,7 +236,10 @@ impl Manager {

// Existing entry, check if the requisite patches have changed before saving.
Entry::Occupied(mut entry) => {
let changed = *entry.get() != version;
let old = entry.get();
version.ban_time = old.ban_time;

let changed = *old.repositories != version.repositories;
if changed {
entry.insert(version.clone());
}
Expand All @@ -232,12 +256,14 @@ impl Manager {

tracing::info!(%key, "new or updated version");

// Update latest tag.
// Update latest tag unless the version has been banned.
// TODO: This might need to be moved to manual-only for now? If there's any long-running ingestion tasks (i.e. search) hanging off versions, then setting latest _now_ would leave end-consumers pointing at an uningested tag.
self.names
.write()
.expect("poisoned")
.insert(TAG_LATEST.to_string(), key);
if version.ban_time.is_none() {
self.names
.write()
.expect("poisoned")
.insert(TAG_LATEST.to_string(), key);
}

// Persist updated metadata
tokio::try_join!(
Expand Down
38 changes: 30 additions & 8 deletions src/version/version.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{path::PathBuf, time::SystemTime};

use anyhow::Result;
use nonempty::NonEmpty;
Expand All @@ -7,25 +7,39 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, PartialEq)]
pub struct Version {
pub repositories: Vec<Repository>,
pub ban_time: Option<SystemTime>,
}

#[derive(Serialize, Deserialize)]
struct PersistedVersion(Vec<PersistedRepository>);
#[serde(untagged)]
enum PersistedVersion {
V2(PersistedVersionV2),
V1(Vec<PersistedRepository>),
}

#[derive(Serialize, Deserialize)]
struct PersistedVersionV2 {
repositories: Vec<PersistedRepository>,
ban_time: Option<SystemTime>,
}

// NOTE: This using using `impl Serialize` so it doesn't become public API surface.
impl Version {
pub(super) fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok> {
let persisted_version = PersistedVersion(
self.repositories
let persisted_version = PersistedVersionV2 {
repositories: self
.repositories
.iter()
.map(|repository| PersistedRepository {
name: repository.name.clone(),
patches: repository.patches.clone().map(|patch| patch.name),
})
.collect(),
);

persisted_version
ban_time: self.ban_time,
};

PersistedVersion::V2(persisted_version)
.serialize(serializer)
.map_err(|err| anyhow::anyhow!(err.to_string()))
}
Expand All @@ -36,9 +50,14 @@ impl Version {
deserializer: D,
get_path: impl Fn(&str, &str) -> PathBuf,
) -> Result<Self> {
let PersistedVersion(persisted_repositories) = PersistedVersion::deserialize(deserializer)
let persisted_version = PersistedVersion::deserialize(deserializer)
.map_err(|err| anyhow::anyhow!(err.to_string()))?;

let (persisted_repositories, ban_time) = match persisted_version {
PersistedVersion::V1(repositories) => (repositories, None),
PersistedVersion::V2(version) => (version.repositories, version.ban_time),
};

let repositories = persisted_repositories
.into_iter()
.map(|persisted_repository| Repository {
Expand All @@ -51,7 +70,10 @@ impl Version {
})
.collect();

Ok(Version { repositories })
Ok(Version {
repositories,
ban_time,
})
}
}

Expand Down

0 comments on commit cb6f45d

Please sign in to comment.