work on repo api

This commit is contained in:
mbecker20
2023-07-03 20:09:44 +00:00
parent c6290cb7f6
commit c25e05fde7
15 changed files with 600 additions and 98 deletions

View File

@@ -1,6 +1,6 @@
use monitor_types::entities::{
build::Build, builder::Builder, deployment::Deployment, server::Server, update::Update,
user::User,
build::Build, builder::Builder, deployment::Deployment, repo::Repo, server::Server,
update::Update, user::User,
};
use mungos::{Collection, Indexed, Mungos};
@@ -12,6 +12,7 @@ pub struct DbClient {
pub deployments: Collection<Deployment>,
pub builds: Collection<Build>,
pub builders: Collection<Builder>,
pub repos: Collection<Repo>,
pub updates: Collection<Update>,
}
@@ -28,6 +29,7 @@ impl DbClient {
deployments: Deployment::collection(&mungos, &config.mongo.db_name, true).await?,
builds: Build::collection(&mungos, &config.mongo.db_name, true).await?,
builders: Builder::collection(&mungos, &config.mongo.db_name, true).await?,
repos: Repo::collection(&mungos, &config.mongo.db_name, true).await?,
updates: Update::collection(&mungos, &config.mongo.db_name, true).await?,
};
Ok(client)

View File

@@ -7,6 +7,7 @@ use monitor_types::{
build::Build,
builder::Builder,
deployment::{Deployment, DockerContainerState},
repo::Repo,
server::{Server, ServerStatus},
update::Update,
user::User,
@@ -264,6 +265,45 @@ impl State {
Ok(builder.get_user_permissions(user_id))
}
pub async fn get_repo(&self, repo_id: &str) -> anyhow::Result<Repo> {
self.db
.repos
.find_one_by_id(repo_id)
.await?
.context(format!("did not find any repo with id {repo_id}"))
}
pub async fn get_repo_check_permissions(
&self,
repo_id: &str,
user: &RequestUser,
permission_level: PermissionLevel,
) -> anyhow::Result<Repo> {
let repo = self
.db
.repos
.find_one_by_id(repo_id)
.await?
.context(format!("did not find any repo with id {repo_id}"))?;
let permissions = repo.get_user_permissions(&user.id);
if user.is_admin || permissions >= permission_level {
Ok(repo)
} else {
Err(anyhow!(
"user does not have required permissions on this repo"
))
}
}
pub async fn get_user_permission_on_repo(
&self,
user_id: &str,
repo_id: &str,
) -> anyhow::Result<PermissionLevel> {
let repo = self.get_repo(repo_id).await?;
Ok(repo.get_user_permissions(user_id))
}
pub async fn send_update(&self, update: Update) -> anyhow::Result<()> {
self.update.sender.lock().await.send(update)?;
Ok(())

View File

@@ -329,89 +329,109 @@ impl Resolve<DeleteDeployment, RequestUser> for State {
return Err(anyhow!("deployment busy"));
}
let start_ts = monitor_timestamp();
let deployment = self
.get_deployment_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
let state = self
.get_deployment_state(&deployment)
.await
.context("failed to get container state")?;
let inner = || async move {
let start_ts = monitor_timestamp();
let mut update = Update {
target: ResourceTarget::Deployment(id.clone()),
operation: Operation::DeleteDeployment,
start_ts,
operator: user.id.clone(),
success: true,
status: UpdateStatus::InProgress,
..Default::default()
};
let state = self
.get_deployment_state(&deployment)
.await
.context("failed to get container state")?;
update.id = self.add_update(update.clone()).await?;
let mut update = Update {
target: ResourceTarget::Deployment(deployment.id.clone()),
operation: Operation::DeleteDeployment,
start_ts,
operator: user.id.clone(),
success: true,
status: UpdateStatus::InProgress,
..Default::default()
};
if !matches!(
state,
DockerContainerState::NotDeployed | DockerContainerState::Unknown
) {
// container needs to be destroyed
let server = self.get_server(&deployment.config.server_id).await;
if let Err(e) = server {
update.logs.push(Log::error(
"remove container",
format!(
"failed to retrieve server at {} from mongo | {e:#?}",
deployment.config.server_id
),
));
} else {
let server = server.unwrap();
match self
.periphery_client(&server)
.request(requests::RemoveContainer {
name: deployment.name.clone(),
signal: deployment.config.termination_signal.into(),
time: deployment.config.termination_timeout.into(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.logs.push(Log::error(
update.id = self.add_update(update.clone()).await?;
if !matches!(
state,
DockerContainerState::NotDeployed | DockerContainerState::Unknown
) {
// container needs to be destroyed
let server = self.get_server(&deployment.config.server_id).await;
if let Err(e) = server {
update.logs.push(Log::error(
"remove container",
format!("failed to remove container on periphery | {e:#?}"),
)),
format!(
"failed to retrieve server at {} from mongo | {e:#?}",
deployment.config.server_id
),
));
} else {
let server = server.unwrap();
match self
.periphery_client(&server)
.request(requests::RemoveContainer {
name: deployment.name.clone(),
signal: deployment.config.termination_signal.into(),
time: deployment.config.termination_timeout.into(),
})
.await
{
Ok(log) => update.logs.push(log),
Err(e) => update.logs.push(Log::error(
"remove container",
format!("failed to remove container on periphery | {e:#?}"),
)),
}
}
}
}
let res = self
.db
.deployments
.delete_one(&id)
.await
.context("failed to delete deployment from mongo");
let res = self
.db
.deployments
.delete_one(&deployment.id)
.await
.context("failed to delete deployment from mongo");
let log = match res {
Ok(_) => Log::simple(
"delete deployment",
format!("deleted deployment {}", deployment.name),
),
Err(e) => Log::error(
"delete deployment",
format!("failed to delete deployment\n{e:#?}"),
),
let log = match res {
Ok(_) => Log::simple(
"delete deployment",
format!("deleted deployment {}", deployment.name),
),
Err(e) => Log::error(
"delete deployment",
format!("failed to delete deployment\n{e:#?}"),
),
};
update.logs.push(log);
update.end_ts = Some(monitor_timestamp());
update.status = UpdateStatus::Complete;
update.success = all_logs_success(&update.logs);
self.update_update(update).await?;
Ok(deployment)
};
update.logs.push(log);
update.end_ts = Some(monitor_timestamp());
update.status = UpdateStatus::Complete;
update.success = all_logs_success(&update.logs);
self.action_states
.deployment
.update_entry(id.clone(), |entry| {
entry.deleting = true;
})
.await;
self.update_update(update).await?;
let res = inner().await;
Ok(deployment)
self.action_states
.deployment
.update_entry(id, |entry| {
entry.deleting = false;
})
.await;
res
}
}

View File

@@ -18,6 +18,7 @@ use crate::{
mod build;
mod deployment;
mod permissions;
mod repo;
mod secret;
mod server;

View File

@@ -5,6 +5,7 @@ use monitor_types::{
build::Build,
builder::Builder,
deployment::Deployment,
repo::Repo,
server::Server,
update::{Log, ResourceTarget, Update, UpdateStatus},
Operation,
@@ -204,6 +205,28 @@ impl Resolve<UpdateUserPermissionsOnTarget, RequestUser> for State {
user.username, permission, server.name
)
}
ResourceTarget::Repo(id) => {
let repo = self
.db
.repos
.find_one_by_id(id)
.await
.context("failed at find repo query")?
.ok_or(anyhow!("failed to find a repo with id {id}"))?;
self.db
.repos
.update_one::<Repo>(
id,
mungos::Update::Set(doc! {
format!("permissions.{}", user_id): permission.to_string()
}),
)
.await?;
format!(
"user {} given {} permissions on repo {}",
user.username, permission, repo.name
)
}
};
let mut update = Update {
operation: Operation::UpdateUserPermissionsOnTarget,

View File

@@ -0,0 +1,354 @@
use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_types::{
entities::{
repo::Repo,
update::{Log, ResourceTarget, Update},
Operation, PermissionLevel,
},
monitor_timestamp,
permissioned::Permissioned,
requests::api::{
CloneRepo, CopyRepo, CreateRepo, DeleteRepo, GetRepo, ListRepos, PullRepo, UpdateRepo,
},
};
use resolver_api::Resolve;
use crate::{auth::RequestUser, state::State};
#[async_trait]
impl Resolve<GetRepo, RequestUser> for State {
async fn resolve(&self, GetRepo { id }: GetRepo, user: RequestUser) -> anyhow::Result<Repo> {
self.get_repo_check_permissions(&id, &user, PermissionLevel::Read)
.await
}
}
#[async_trait]
impl Resolve<ListRepos, RequestUser> for State {
async fn resolve(
&self,
ListRepos { query }: ListRepos,
user: RequestUser,
) -> anyhow::Result<Vec<Repo>> {
let repos = self
.db
.repos
.get_some(query, None)
.await
.context("failed to pull repos from mongo")?;
let repos = if user.is_admin {
repos
} else {
repos
.into_iter()
.filter(|repo| repo.get_user_permissions(&user.id) > PermissionLevel::None)
.collect()
};
Ok(repos)
}
}
#[async_trait]
impl Resolve<CreateRepo, RequestUser> for State {
async fn resolve(
&self,
CreateRepo { name, config }: CreateRepo,
user: RequestUser,
) -> anyhow::Result<Repo> {
if let Some(server_id) = &config.server_id {
if !server_id.is_empty() {
self.get_server_check_permissions(
server_id,
&user,
PermissionLevel::Update,
)
.await
.context("cannot create repo on this server. user must have update permissions on the server.")?;
}
}
let start_ts = monitor_timestamp();
let repo = Repo {
id: Default::default(),
name,
created_at: start_ts,
updated_at: start_ts,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
description: Default::default(),
config: config.into(),
};
let repo_id = self
.db
.repos
.create_one(repo)
.await
.context("failed to add repo to db")?;
let repo = self.get_repo(&repo_id).await?;
let update = Update {
target: ResourceTarget::Repo(repo_id),
operation: Operation::CreateRepo,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create repo",
format!("created repo\nid: {}\nname: {}", repo.id, repo.name),
),
Log::simple("config", format!("{:#?}", repo.config)),
],
..Default::default()
};
self.add_update(update).await?;
if !repo.config.repo.is_empty() && !repo.config.server_id.is_empty() {
let _ = self
.resolve(
CloneRepo {
id: repo.id.clone(),
},
user,
)
.await;
}
Ok(repo)
}
}
#[async_trait]
impl Resolve<CopyRepo, RequestUser> for State {
async fn resolve(
&self,
CopyRepo { name, id }: CopyRepo,
user: RequestUser,
) -> anyhow::Result<Repo> {
let Repo {
config,
description,
..
} = self
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
if !config.server_id.is_empty() {
self.get_server_check_permissions(
&config.server_id,
&user,
PermissionLevel::Update,
)
.await
.context("cannot create repo on this server. user must have update permissions on the server.")?;
}
let start_ts = monitor_timestamp();
let repo = Repo {
id: Default::default(),
name,
created_at: start_ts,
updated_at: start_ts,
permissions: [(user.id.clone(), PermissionLevel::Update)]
.into_iter()
.collect(),
description,
config,
};
let repo_id = self
.db
.repos
.create_one(repo)
.await
.context("failed to add repo to db")?;
let repo = self.get_repo(&repo_id).await?;
let update = Update {
target: ResourceTarget::Repo(repo_id),
operation: Operation::CreateRepo,
start_ts,
end_ts: Some(monitor_timestamp()),
operator: user.id.clone(),
success: true,
logs: vec![
Log::simple(
"create repo",
format!("created repo\nid: {}\nname: {}", repo.id, repo.name),
),
Log::simple("config", format!("{:#?}", repo.config)),
],
..Default::default()
};
self.add_update(update).await?;
Ok(repo)
}
}
#[async_trait]
impl Resolve<DeleteRepo, RequestUser> for State {
async fn resolve(
&self,
DeleteRepo { id }: DeleteRepo,
user: RequestUser,
) -> anyhow::Result<Repo> {
let repo = self
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
let inner = || async move {
let start_ts = monitor_timestamp();
todo!()
};
if self.action_states.repo.busy(&id).await {
return Err(anyhow!("repo busy"));
}
self.action_states
.repo
.update_entry(repo.id.clone(), |entry| {
entry.deleting = true;
})
.await;
let res = inner().await;
self.action_states
.repo
.update_entry(repo.id, |entry| {
entry.deleting = false;
})
.await;
res
}
}
#[async_trait]
impl Resolve<UpdateRepo, RequestUser> for State {
async fn resolve(
&self,
UpdateRepo { id, config }: UpdateRepo,
user: RequestUser,
) -> anyhow::Result<Repo> {
let repo = self
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
let inner = || async move {
let start_ts = monitor_timestamp();
todo!()
};
if self.action_states.repo.busy(&id).await {
return Err(anyhow!("repo busy"));
}
self.action_states
.repo
.update_entry(repo.id.clone(), |entry| {
entry.updating = true;
})
.await;
let res = inner().await;
self.action_states
.repo
.update_entry(repo.id, |entry| {
entry.updating = false;
})
.await;
res
}
}
#[async_trait]
impl Resolve<CloneRepo, RequestUser> for State {
async fn resolve(
&self,
CloneRepo { id }: CloneRepo,
user: RequestUser,
) -> anyhow::Result<Update> {
let repo = self
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
let inner = || async move {
let start_ts = monitor_timestamp();
todo!()
};
if self.action_states.repo.busy(&id).await {
return Err(anyhow!("repo busy"));
}
self.action_states
.repo
.update_entry(repo.id.clone(), |entry| {
entry.cloning = true;
})
.await;
let res = inner().await;
self.action_states
.repo
.update_entry(repo.id, |entry| {
entry.cloning = false;
})
.await;
res
}
}
#[async_trait]
impl Resolve<PullRepo, RequestUser> for State {
async fn resolve(
&self,
PullRepo { id }: PullRepo,
user: RequestUser,
) -> anyhow::Result<Update> {
let repo = self
.get_repo_check_permissions(&id, &user, PermissionLevel::Update)
.await?;
let inner = || async move {
let start_ts = monitor_timestamp();
todo!()
};
if self.action_states.repo.busy(&id).await {
return Err(anyhow!("repo busy"));
}
self.action_states
.repo
.update_entry(repo.id.clone(), |entry| {
entry.pulling = true;
})
.await;
let res = inner().await;
self.action_states
.repo
.update_entry(repo.id, |entry| {
entry.pulling = false;
})
.await;
res
}
}

View File

@@ -4,7 +4,8 @@ use anyhow::Context;
use axum::Extension;
use monitor_types::{
entities::{
build::BuildActionState, deployment::DeploymentActionState, server::ServerActionState,
build::BuildActionState, deployment::DeploymentActionState, repo::RepoActionState,
server::ServerActionState,
},
requests::auth::GetLoginOptionsResponse,
};
@@ -99,5 +100,6 @@ pub struct ActionStates {
pub build: Cache<BuildActionState>,
pub deployment: Cache<DeploymentActionState>,
pub server: Cache<ServerActionState>,
pub repo: Cache<RepoActionState>,
// pub command: Cache<CommandActionState>,
}

View File

@@ -188,6 +188,10 @@ impl State {
.await?;
(permissions, "builder")
}
ResourceTarget::Repo(repo_id) => {
let permissions = self.get_user_permission_on_repo(user_id, repo_id).await?;
(permissions, "repo")
}
// UpdateTarget::Procedure(procedure_id) => {
// let permissions = db_client
// .get_user_permission_on_procedure(user_id, procedure_id)

View File

@@ -1,5 +1,6 @@
use crate::entities::{
build::BuildActionState, deployment::DeploymentActionState, server::ServerActionState,
build::BuildActionState, deployment::DeploymentActionState, repo::RepoActionState,
server::ServerActionState,
};
pub trait Busy {
@@ -15,13 +16,12 @@ impl Busy for ServerActionState {
impl Busy for DeploymentActionState {
fn busy(&self) -> bool {
self.deploying
|| self.pulling
|| self.recloning
|| self.removing
|| self.starting
|| self.stopping
|| self.updating
|| self.renaming
|| self.deleting
}
}
@@ -30,3 +30,9 @@ impl Busy for BuildActionState {
self.building || self.updating
}
}
impl Busy for RepoActionState {
fn busy(&self) -> bool {
self.cloning || self.pulling || self.updating || self.deleting
}
}

View File

@@ -319,8 +319,7 @@ pub struct DeploymentActionState {
pub stopping: bool,
pub starting: bool,
pub removing: bool,
pub pulling: bool,
pub recloning: bool,
pub updating: bool,
pub renaming: bool,
pub deleting: bool,
}

View File

@@ -252,6 +252,11 @@ pub enum Operation {
DeleteBuild,
RunBuild,
// builder
CreateBuilder,
UpdateBuilder,
DeleteBuilder,
// deployment
CreateDeployment,
UpdateDeployment,
@@ -264,21 +269,28 @@ pub enum Operation {
RecloneDeployment,
RenameDeployment,
// procedure
CreateProcedure,
UpdateProcedure,
DeleteProcedure,
// repo
CreateRepo,
UpdateRepo,
DeleteRepo,
CloneRepo,
PullRepo,
// command
CreateCommand,
UpdateCommand,
DeleteCommand,
RunCommand,
// // procedure
// CreateProcedure,
// UpdateProcedure,
// DeleteProcedure,
// group
CreateGroup,
UpdateGroup,
DeleteGroup,
// // command
// CreateCommand,
// UpdateCommand,
// DeleteCommand,
// RunCommand,
// // group
// CreateGroup,
// UpdateGroup,
// DeleteGroup,
// user
UpdateUserPermissions,

View File

@@ -1,13 +1,16 @@
use bson::serde_helpers::hex_string_as_object_id;
use derive_builder::Builder;
use mungos::MungosIndexed;
use partial_derive2::Partial;
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::{i64_is_zero, I64};
use super::{PermissionsMap, SystemCommand};
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, MungosIndexed)]
pub struct Repo {
#[serde(
default,
@@ -18,6 +21,7 @@ pub struct Repo {
#[builder(setter(skip))]
pub id: String,
#[unique_index]
pub name: String,
#[serde(default)]
@@ -28,22 +32,23 @@ pub struct Repo {
#[builder(setter(skip))]
pub permissions: PermissionsMap,
#[serde(default, skip_serializing_if = "String::is_empty")]
#[serde(default, skip_serializing_if = "i64_is_zero")]
#[builder(setter(skip))]
pub created_at: String,
pub created_at: I64,
#[serde(default)]
#[builder(setter(skip))]
pub updated_at: String,
pub updated_at: I64,
pub config: RepoConfig,
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)]
#[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial, MungosIndexed)]
#[partial_derive(Serialize, Deserialize, Debug, Clone)]
#[skip_serializing_none]
pub struct RepoConfig {
#[index]
pub server_id: String,
pub repo: String,
@@ -82,7 +87,16 @@ impl From<PartialRepoConfig> for RepoConfig {
github_account: value.github_account.unwrap_or_default(),
on_clone: value.on_clone.unwrap_or_default(),
on_pull: value.on_pull.unwrap_or_default(),
tags: value.tags.unwrap_or_default()
tags: value.tags.unwrap_or_default(),
}
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct RepoActionState {
pub cloning: bool,
pub pulling: bool,
pub updating: bool,
pub deleting: bool,
}

View File

@@ -95,6 +95,7 @@ pub enum ResourceTarget {
Builder(String),
Deployment(String),
Server(String),
Repo(String),
// Procedure(String),
// Group(String),
// Command(String),

View File

@@ -1,6 +1,6 @@
use crate::entities::{
build::Build, builder::Builder, deployment::Deployment, server::Server, PermissionLevel,
PermissionsMap,
build::Build, builder::Builder, deployment::Deployment, repo::Repo, server::Server,
PermissionLevel, PermissionsMap,
};
pub trait Permissioned {
@@ -34,3 +34,9 @@ impl Permissioned for Builder {
&self.permissions
}
}
impl Permissioned for Repo {
fn permissions_map(&self) -> &PermissionsMap {
&self.permissions
}
}

View File

@@ -3,7 +3,10 @@ use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::{
entities::repo::{PartialRepoConfig, Repo},
entities::{
repo::{PartialRepoConfig, Repo},
update::Update,
},
MongoDocument,
};
@@ -66,3 +69,18 @@ pub struct UpdateRepo {
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(Update)]
pub struct CloneRepo {
pub id: String,
}
//
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Request)]
#[response(Update)]
pub struct PullRepo {
pub id: String,
}