add service user replacement in procedure

This commit is contained in:
mbecker20
2024-04-01 04:18:19 -07:00
parent 8f5570128d
commit ee22044b34
18 changed files with 232 additions and 50 deletions

View File

@@ -33,7 +33,7 @@ impl Resolve<RunProcedure, User> for State {
.await?;
let mut update =
make_update(&procedure, Operation::StopContainer, &user);
make_update(&procedure, Operation::RunProcedure, &user);
update.in_progress();
update.push_simple_log(
"execute procedure",

View File

@@ -8,7 +8,7 @@ use monitor_client::entities::{
server::{Server, ServerStatus},
tag::Tag,
update::{ResourceTarget, Update, UpdateListItem},
user::User,
user::{admin_service_user, User},
Operation,
};
use mungos::{
@@ -60,6 +60,9 @@ pub fn make_update(
}
pub async fn get_user(user_id: &str) -> anyhow::Result<User> {
if let Some(user) = admin_service_user(user_id) {
return Ok(user);
}
find_one_by_id(&db_client().await.users, user_id)
.await
.context("failed to query mongo for user")?

View File

@@ -8,7 +8,7 @@ use monitor_client::{
monitor_timestamp,
procedure::{EnabledExecution, Procedure, ProcedureConfig},
update::Update,
user::User,
user::procedure_user,
},
};
use resolver_api::Resolve;
@@ -123,7 +123,7 @@ async fn execute_execution(
parent_id: &str,
parent_name: &str,
) -> anyhow::Result<()> {
let user = User::admin_service_user("procedure");
let user = procedure_user().to_owned();
let update = match execution {
Execution::None(_) => return Ok(()),
Execution::RunProcedure(req) => {

View File

@@ -4,7 +4,7 @@ use hex::ToHex;
use hmac::{Hmac, Mac};
use monitor_client::{
api::execute,
entities::{build::Build, repo::Repo, user::User},
entities::{build::Build, repo::Repo, user::github_user},
};
use resolver_api::Resolve;
use serde::Deserialize;
@@ -80,7 +80,7 @@ async fn handle_build_webhook(
State
.resolve(
execute::RunBuild { build: build_id },
User::admin_service_user("github"),
github_user().to_owned(),
)
.await?;
Ok(())
@@ -100,7 +100,7 @@ async fn handle_repo_clone_webhook(
State
.resolve(
execute::CloneRepo { repo: repo_id },
User::admin_service_user("github"),
github_user().to_owned(),
)
.await?;
Ok(())
@@ -120,7 +120,7 @@ async fn handle_repo_pull_webhook(
State
.resolve(
execute::PullRepo { repo: repo_id },
User::admin_service_user("github"),
github_user().to_owned(),
)
.await?;
Ok(())

View File

@@ -20,6 +20,14 @@ config.build_path = "."
config.dockerfile_path = "frontend/Dockerfile"
config.docker_account = "mbecker2020"
[[build]]
name = "test-build"
tags = ["test"]
config.builder = "mogh-builder"
config.repo = "mbecker20/docker_build_test"
config.github_account = "mbecker20"
config.docker_account = "mbecker2020"
[[builder]]
name = "mogh-builder"
description = "the builder for the mogh images"

View File

@@ -20,4 +20,11 @@ tags = ["monitor", "v1", "frontend"]
config.server = "mogh-server"
config.redeploy_on_build = true
config.image.type = "Build"
config.image.params.build = "monitor-frontend-v1"
config.image.params.build = "monitor-frontend-v1"
[[deployment]]
name = "hello-world"
tags = ["test"]
config.server = "mogh-server"
config.image.type = "Image"
config.image.params.image = "hello-world"

View File

@@ -1,3 +1,5 @@
use std::sync::OnceLock;
use mongo_indexed::derive::MongoIndexed;
use mungos::mongodb::bson::{
doc, serde_helpers::hex_string_as_object_id, Document,
@@ -60,18 +62,40 @@ impl User {
self.config = UserConfig::default();
}
}
}
pub fn admin_service_user(id_name: impl Into<String>) -> User {
let id_name: String = id_name.into();
pub fn admin_service_user(user_id: &str) -> Option<User> {
match user_id {
"Procedure" => procedure_user().to_owned().into(),
"Github" => github_user().to_owned().into(),
_ => None,
}
}
pub fn procedure_user() -> &'static User {
static PROCEDURE_USER: OnceLock<User> = OnceLock::new();
PROCEDURE_USER.get_or_init(|| {
let id_name = String::from("Procedure");
User {
id: id_name.clone(),
username: id_name,
admin: true,
create_build_permissions: true,
create_server_permissions: true,
..Default::default()
}
}
})
}
pub fn github_user() -> &'static User {
static PROCEDURE_USER: OnceLock<User> = OnceLock::new();
PROCEDURE_USER.get_or_init(|| {
let id_name = String::from("Github");
User {
id: id_name.clone(),
username: id_name,
admin: true,
..Default::default()
}
})
}
#[typeshare]

View File

@@ -4,6 +4,7 @@ import {
ConfirmUpdate,
} from "@components/config/util";
import { Section } from "@components/layouts";
import { snake_case_to_upper_space_case } from "@lib/formatting";
import { Types } from "@monitor/client";
import { Button } from "@ui/button";
import { Card, CardHeader, CardTitle, CardContent } from "@ui/card";
@@ -122,9 +123,11 @@ export const Config = <T,>({
<div className="flex flex-col gap-6 min-h-[500px] w-full">
{Object.entries(components[show]).map(([k, v]) => (
<Card className="w-full" key={k}>
<CardHeader className="border-b">
<CardTitle className="capitalize">{k} Settings</CardTitle>
</CardHeader>
{k && (
<CardHeader className="border-b">
<CardTitle>{snake_case_to_upper_space_case(k)}</CardTitle>
</CardHeader>
)}
<CardContent className="flex flex-col gap-4 mt-4">
<ConfigAgain
config={config}

View File

@@ -62,19 +62,27 @@ export const ConfigItem = ({
export const ConfigInput = ({
label,
value,
placeholder,
disabled,
onChange,
onBlur,
}: {
label: string;
value: string | number | undefined;
onChange: (value: string) => void;
onChange?: (value: string) => void;
onBlur?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
}) => (
<ConfigItem label={label}>
<Input
className="max-w-[75%] lg:max-w-[400px]"
type={typeof value === "number" ? "number" : undefined}
value={value}
onChange={(e) => onChange(e.target.value)}
// disabled={loading}
onChange={(e) => onChange && onChange(e.target.value)}
onBlur={(e) => onBlur && onBlur(e.target.value)}
placeholder={placeholder}
disabled={disabled}
/>
</ConfigItem>
);
@@ -109,6 +117,7 @@ export const DoubleInput = <
onRightChange,
onAdd,
onRemove,
inputClassName,
}: {
values: T[] | undefined;
leftval: L;
@@ -120,18 +129,21 @@ export const DoubleInput = <
onRightChange: (value: T[R], i: number) => void;
onAdd: () => void;
onRemove: (i: number) => void;
inputClassName?: string;
}) => {
return (
<div className="flex flex-col gap-4">
{values?.map((value, i) => (
<div className="flex items-center justify-between gap-4" key={i}>
<Input
className={inputClassName}
value={value[leftval] as any}
placeholder={leftpl}
onChange={(e) => onLeftChange(e.target.value as T[L], i)}
/>
:
<Input
className={inputClassName}
value={value[rightval] as any}
placeholder={rightpl}
onChange={(e) => onRightChange(e.target.value as T[R], i)}
@@ -200,7 +212,7 @@ export const ResourceSelector = ({
{`No ${type}s Found`}
<SearchX className="w-3 h-3" />
</CommandEmpty>
<CommandGroup>
{resources.map((resource) => (
<CommandItem
@@ -235,9 +247,11 @@ export const AccountSelector = ({
selected: string | undefined;
onSelect: (id: string) => void;
}) => {
const request =
type === "Server" ? "GetAvailableAccounts" : "GetBuilderAvailableAccounts";
const accounts = useRead(request, { server: id! }, { enabled: !!id }).data;
const [request, params] =
type === "Server"
? ["GetAvailableAccounts", { server: id! }]
: ["GetBuilderAvailableAccounts", { builder: id }];
const accounts = useRead(request as any, params, { enabled: !!id }).data;
return (
<ConfigItem label={`${account_type} Account`}>
<Select
@@ -254,7 +268,7 @@ export const AccountSelector = ({
{type === "Server" && (
<SelectItem value={" "}>Same as build</SelectItem>
)}
{accounts?.[account_type]?.map((account) => (
{(accounts as any)?.[account_type]?.map((account: string) => (
<SelectItem key={account} value={account}>
{account}
</SelectItem>

View File

@@ -22,7 +22,7 @@ export const EnvVars = ({
className="flex-col gap-4 items-start"
>
<Textarea
className="min-h-[300px]"
className="min-h-[400px]"
placeholder="VARIABLE=value"
value={env}
onChange={(e) => setEnv(e.target.value)}

View File

@@ -31,7 +31,7 @@ export const NetworkModeSelector = ({
</SelectTrigger>
<SelectContent>
{networks?.map((network) => (
<SelectItem key={network.Id} value={network.Name ?? ""}>
<SelectItem key={network.Id} value={network.Name ?? ""} className="cursor-pointer">
{network.Name}
</SelectItem>
))}

View File

@@ -31,9 +31,11 @@ export const RestartModeSelector = ({
<SelectItem
key={mode}
value={Types.RestartMode[mode]}
className="capitalize"
className="capitalize cursor-pointer"
>
{format_mode(Types.RestartMode[mode])}
{mode === "NoRestart"
? "Don't Restart"
: format_mode(Types.RestartMode[mode])}
</SelectItem>
))}
</SelectContent>

View File

@@ -0,0 +1,101 @@
import { ConfigInput, ConfigItem } from "@components/config/util";
import { Types } from "@monitor/client";
import { Input } from "@ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@ui/select";
import { useToast } from "@ui/use-toast";
import { useEffect, useState } from "react";
export const TerminationSignals = ({
args,
set,
}: {
args: Types.TerminationSignalLabel[];
set: (input: Partial<Types.DeploymentConfig>) => void;
}) => {
return (
<ConfigItem label="Termination Signals">
<div>
{args.map((arg, i) => {
return <></>;
})}
</div>
</ConfigItem>
);
};
export const DefaultTerminationSignal = ({
arg,
set,
}: {
arg?: Types.TerminationSignal;
set: (input: Partial<Types.DeploymentConfig>) => void;
}) => {
return (
<ConfigItem label="Default Termination Signal">
<Select
value={arg}
onValueChange={(value) =>
set({ termination_signal: value as Types.TerminationSignal })
}
>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Select Type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{Object.values(Types.TerminationSignal)
.reverse()
.map((term_signal) => (
<SelectItem key={term_signal} value={term_signal} className="cursor-pointer">
{term_signal}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</ConfigItem>
);
};
export const TerminationTimeout = ({
arg,
set,
}: {
arg: number;
set: (input: Partial<Types.DeploymentConfig>) => void;
}) => {
const { toast } = useToast();
const [input, setInput] = useState(arg.toString());
useEffect(() => {
setInput(arg.toString());
}, [arg]);
return (
<ConfigItem label="Termination Timeout">
<div className="flex items-center justify-between gap-4">
<Input
className="w-[100px]"
placeholder="time in seconds"
value={input}
onChange={(e) => setInput(e.target.value)}
onBlur={(e) => {
const num = Number(e.target.value);
if (num || num === 0) {
set({ termination_timeout: num });
} else {
toast({ title: "Termination timeout must be a number" });
setInput(arg.toString());
}
}}
/>
seconds
</div>
</ConfigItem>
);
};

View File

@@ -10,6 +10,7 @@ export const VolumesConfig = ({
}) => (
<ConfigItem label="Volumes" className="items-start">
<DoubleInput
inputClassName="w-[300px] max-w-full"
values={volumes}
leftval="local"
leftpl="Local"

View File

@@ -15,6 +15,10 @@ import { EnvVars } from "./components/environment";
import { VolumesConfig } from "./components/volumes";
import { ExtraArgs } from "./components/extra-args";
import { Config } from "@components/config";
import {
DefaultTerminationSignal,
TerminationTimeout,
} from "./components/term-signal";
export const ServerSelector = ({
selected,
@@ -53,7 +57,7 @@ export const DeploymentConfig = ({ id }: { id: string }) => {
onSave={() => mutate({ id, config: update })}
components={{
general: {
general: {
"": {
server_id: (value, set) => (
<ServerSelector selected={value} set={set} />
),
@@ -72,9 +76,6 @@ export const DeploymentConfig = ({ id }: { id: string }) => {
restart: (value, set) => (
<RestartModeSelector selected={value} set={set} />
),
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} />
),
process_args: (value, set) => (
<ConfigInput
label="Process Args"
@@ -97,6 +98,23 @@ export const DeploymentConfig = ({ id }: { id: string }) => {
volumes: {
volumes: (v, set) => <VolumesConfig volumes={v ?? []} set={set} />,
},
extra_args: {
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} />
),
},
termination: {
termination_signal: (value, set) => (
<DefaultTerminationSignal arg={value} set={set} />
),
termination_timeout: (value, set) => (
<TerminationTimeout arg={value} set={set} />
),
},
settings: {
send_alerts: true,
redeploy_on_build: true,
},
},
environment: {
environment: {

View File

@@ -93,7 +93,7 @@ export const DeploymentLogs = ({ id }: { id: string }) => {
>
{["stdout", "stderr"].map((t) => (
<TabsContent key={t} className="h-full relative" value={t}>
<div className="h-[60vh] overflow-y-auto">
<div className="h-[70vh] overflow-y-auto">
<pre id={t} className="-scroll-mt-24">
{logs?.[t as keyof typeof logs] || `no ${t} logs`}
</pre>

View File

@@ -49,22 +49,22 @@ export const WithLoading = ({
return <>{children}</>;
};
export const ConfigInput = ({
placeholder,
value,
onChange,
}: {
placeholder: string;
value: string | undefined;
onChange: (s: string) => void;
}) => (
<Input
placeholder={placeholder}
className="max-w-[500px]"
value={value}
onChange={({ target }) => onChange(target.value)}
/>
);
// export const ConfigInput = ({
// placeholder,
// value,
// onChange,
// }: {
// placeholder: string;
// value: string | undefined;
// onChange: (s: string) => void;
// }) => (
// <Input
// placeholder={placeholder}
// className="max-w-[500px]"
// value={value}
// onChange={({ target }) => onChange(target.value)}
// />
// );
export const ActionButton = forwardRef<
HTMLButtonElement,

View File

@@ -32,6 +32,7 @@ export const fmt_duration = (start_ts: number, end_ts: number) => {
/// list_all_items => List All Items
export function snake_case_to_upper_space_case(snake: string) {
if (snake.length === 0) return "";
return snake
.split("_")
.map((item) => item[0].toUpperCase() + item.slice(1))