Skip to content

Commit

Permalink
Merge pull request #20 from MathisBurger/feature/runner-cmd
Browse files Browse the repository at this point in the history
[FEAT] Added cmd runner and fixed some bugs
  • Loading branch information
MathisBurger authored Oct 8, 2024
2 parents 2dede1a + e2436bc commit ca522aa
Show file tree
Hide file tree
Showing 29 changed files with 73 additions and 33 deletions.
4 changes: 1 addition & 3 deletions executor/internal/services/TaskBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ import (

func BuildTask(er ExecRequest) (input.Task, error) {
var image string
var run string

switch er.Assignment.Language {
case LanguageGo:
image = "golang:1.19"
run = "go run main.go > $TORK_OUTPUT"
default:
return input.Task{}, errors.New("invalid language")
}
Expand All @@ -31,7 +29,7 @@ func BuildTask(er ExecRequest) (input.Task, error) {
return input.Task{
Name: er.Assignment.Title + " - " + strconv.Itoa(er.Solution.Id),
Image: image,
Run: run,
Run: er.Assignment.RunnerCmd + " > $TORK_OUTPUT",
Limits: &input.Limits{
CPUs: er.Assignment.RunnerCpu,
Memory: er.Assignment.RunnerMemory,
Expand Down
1 change: 1 addition & 0 deletions executor/internal/services/Types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Assignment struct {
RunnerCpu string `json:"runner_cpu"`
RunnerMemory string `json:"runner_memory"`
RunnerTimeout string `json:"runner_timeout"`
RunnerCmd string `json:"runner_cmd"`
}

type ExecRequest struct {
Expand Down
16 changes: 13 additions & 3 deletions tasky/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ COPY ./Cargo.toml ./Cargo.toml
RUN cargo build --release
RUN rm src/*.rs

COPY . .
RUN apt-get update -y && apt-get install --no-install-recommends --assume-yes protobuf-compiler
RUN cargo install diesel_cli --no-default-features --features postgres

COPY . .
RUN apt-get update -y && apt-get install --no-install-recommends --assume-yes protobuf-compiler libldap-2.5-0
RUN rm ./target/release/deps/tasky*
ENV IS_DOCKER="true"
RUN cargo build --release

FROM busybox:1.35.0-uclibc as busybox

FROM gcr.io/distroless/cc-debian12
ARG ARCH=x86_64

COPY --from=busybox:1.35.0-uclibc /bin/sh /bin/sh

COPY --from=build /usr/lib/${ARCH}-linux-gnu/libpq.so* /usr/lib/${ARCH}-linux-gnu/
COPY --from=build /usr/lib/${ARCH}-linux-gnu/libgssapi_krb5.so* /usr/lib/${ARCH}-linux-gnu/
COPY --from=build /usr/lib/${ARCH}-linux-gnu/libldap_r-2.4.so* /usr/lib/${ARCH}-linux-gnu/
Expand All @@ -35,12 +40,17 @@ COPY --from=build /usr/lib/${ARCH}-linux-gnu/libgmp.so* /usr/lib/${ARCH}-linux-g
COPY --from=build /usr/lib/${ARCH}-linux-gnu/libffi.so* /usr/lib/${ARCH}-linux-gnu/
COPY --from=build /lib/${ARCH}-linux-gnu/libcom_err.so* /lib/${ARCH}-linux-gnu/
COPY --from=build /lib/${ARCH}-linux-gnu/libkeyutils.so* /lib/${ARCH}-linux-gnu/
COPY --from=build /lib/${ARCH}-linux-gnu/libldap* /lib/${ARCH}-linux-gnu/
COPY --from=build /lib/${ARCH}-linux-gnu/liblber* /lib/${ARCH}-linux-gnu/


WORKDIR /app
COPY --from=build --chmod=777 /usr/local/cargo/bin/diesel /app/diesel
COPY --from=build /tasky/target/release/tasky /app/tasky
COPY --from=build /tasky/config.toml /app/config.toml
COPY --from=build /tasky/migrations /app/migrations
COPY --from=build --chmod=777 /tasky/migrate.sh /app/migrate.sh

EXPOSE 3000
EXPOSE 3001
CMD ./set_db_uri.sh && ./tasky
CMD ./migrate.sh && ./tasky
1 change: 1 addition & 0 deletions tasky/set_db_uri.sh → tasky/migrate.sh
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export DATABASE_URL=postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOST/$DB_NAME
./diesel migration run
1 change: 1 addition & 0 deletions tasky/migrations/2024-10-08-083007_runner_cmd/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE assignments DROP runner_cmd;
1 change: 1 addition & 0 deletions tasky/migrations/2024-10-08-083007_runner_cmd/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE assignments ADD runner_cmd TEXT NOT NULL DEFAULT 'echo "Hello World!"';
2 changes: 2 additions & 0 deletions tasky/src/handler/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct RunnerData {
pub runner_cpu: String,
pub runner_memory: String,
pub runner_timeout: String,
pub runner_cmd: String,
}

/// Multipart form to create code tests
Expand Down Expand Up @@ -98,6 +99,7 @@ pub async fn handle_create_multipart(
assignment.runner_cpu = form.runner_config.runner_cpu.clone();
assignment.runner_memory = form.runner_config.runner_memory.clone();
assignment.runner_timeout = form.runner_config.runner_timeout.clone();
assignment.runner_cmd = form.runner_config.runner_cmd.clone();
AssignmentRepository::update_assignment(assignment.clone(), db);
Ok(assignment)
}
1 change: 1 addition & 0 deletions tasky/src/handler/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub async fn handle_create_multipart(
submitter_id: user_data.user_id,
assignment_id: assignment.id,
approval_status: Some(ApprovalStatus::Pending.string()),
group_id: assignment.group_id,
};
if !new_solution.is_granted(crate::security::SecurityAction::Create, user_data) {
return Err(ApiError::Forbidden {
Expand Down
1 change: 1 addition & 0 deletions tasky/src/models/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct Assignment {
pub runner_cpu: String,
pub runner_memory: String,
pub runner_timeout: String,
pub runner_cmd: String,
}

/// Used to create a new assignment
Expand Down
1 change: 1 addition & 0 deletions tasky/src/models/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct NewSolution {
pub submitter_id: i32,
pub assignment_id: i32,
pub approval_status: Option<String>,
pub group_id: i32,
}

pub struct SolutionRepository;
Expand Down
15 changes: 14 additions & 1 deletion tasky/src/response/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
models::{assignment::AssignmentRepository, solution::Solution},
};

use super::assignment::MinifiedAssignmentResponse;
use super::{
assignment::{AssignmentFileStructure, AssignmentResponse},
shared::User,
Expand All @@ -31,6 +32,8 @@ pub struct ListSolutionResponse {
pub id: i32,
pub submitter: User,
pub approval_status: Option<String>,
pub assignment: MinifiedAssignmentResponse,
pub job: Option<Job>,
}

/// Vec of solutions
Expand All @@ -43,17 +46,27 @@ impl Enrich<Solution> for ListSolutionResponse {
async fn enrich(
from: &Solution,
client: &mut UsernatorApiClient<tonic::transport::Channel>,
_db_conn: &mut super::DB,
db_conn: &mut super::DB,
) -> Result<Self, ApiError> {
let submitter = client
.get_user(UserRequest {
user_id: u64::try_from(from.submitter_id)?,
})
.await?;
let assignment =
AssignmentRepository::get_assignment_by_id(from.assignment_id, db_conn).unwrap();
let assignment_response =
MinifiedAssignmentResponse::enrich(&assignment, client, db_conn).await?;
let job = match from.job_id.as_ref() {
Some(id) => Some(get_job(id).await?),
None => None,
};
Ok(ListSolutionResponse {
id: from.id,
submitter: submitter.into_inner().into(),
approval_status: from.approval_status.clone(),
assignment: assignment_response,
job,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions tasky/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ diesel::table! {
runner_memory -> Varchar,
#[max_length = 5]
runner_timeout -> Varchar,
runner_cmd -> Text,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import useClientQuery from "@/hooks/useClientQuery";
import {Badge, Button, Container, Group, Tabs, Title} from "@mantine/core";
import AssignmentDateDisplay from "@/components/assignments/AssignmentDateDisplay";
import NavigateBack from "@/components/NavigateBack";
import RichTextDisplay from "@/components/display/RichTextDisplay";
import useCurrentUser from "@/hooks/useCurrentUser";
import {isGranted} from "@/service/auth";
import {UserRoles} from "@/service/types/usernator";
Expand Down
3 changes: 1 addition & 2 deletions web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export default function RootLayout({
<MantineProvider theme={{}}>
<DatesProvider settings={{timezone: null}}>
<Notifications />
{/* @ts-ignore */}
<AppShell header={{height: 100}} navbar={showNavbar ? {width: 250} : undefined}>
<AppShell header={{height: 100}} navbar={showNavbar ? {width: 250, breakpoint: ""} : undefined}>
<AppShell.Header><Header /></AppShell.Header>
{showNavbar && (<AppShell.Navbar><Navbar /></AppShell.Navbar>)}
<AppShell.Main>{children}</AppShell.Main>
Expand Down
4 changes: 2 additions & 2 deletions web/app/solutions/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const SolutionDetailsPage = ({params}: {params: {id: string}}) => {
const {user} = useCurrentUser();
const [executorModalOpen, setExecutorModalOpen] = useState(false);
const [solution, refetch] = useClientQuery<Solution>(() => api.getSolution(id));

console.log(solution)
const approve = async () => {
await api.approveSolution(id);
refetch();
Expand Down Expand Up @@ -52,7 +52,7 @@ const SolutionDetailsPage = ({params}: {params: {id: string}}) => {
<Group>
<Title>{solution.assignment.title} - {solution.id}</Title>
<Badge color="indigo">{solution.submitter.username}</Badge>
<SolutionBadge status={solution.approval_status} />
<SolutionBadge status={solution.approval_status} job={solution.job ?? undefined} />
{isGranted(user, [UserRoles.Admin]) && (
<Button onClick={() => setExecutorModalOpen(true)}>Executor UI</Button>
)}
Expand Down
5 changes: 3 additions & 2 deletions web/app/solutions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
import useApiServiceClient from "@/hooks/useApiServiceClient";
import useClientQuery from "@/hooks/useClientQuery";
import { SolutionsResponse } from "@/service/types/tasky";
import {Badge, Container, Title} from "@mantine/core";
import {Container, Title} from "@mantine/core";
import EntityList, {EntityListCol, EntityListRowAction} from "@/components/EntityList";
import {UserRoles} from "@/service/types/usernator";
import {router} from "next/client";
import SolutionBadge from "@/components/solution/SolutionBadge";
import {useRouter} from "next/navigation";


const PersonalSolutionsPage = () => {

const api = useApiServiceClient();
const router = useRouter();
const [solutions] = useClientQuery<SolutionsResponse>(() => api.getPersonalSolutions());

const cols: EntityListCol[] = [
Expand Down
2 changes: 1 addition & 1 deletion web/components/EntityList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';
import {Button, ButtonProps, Group, Table} from "@mantine/core";
import {UserRoles} from "@/service/types/usernator";
import {useCallback, useMemo} from "react";
import {useMemo} from "react";
import {isGranted} from "@/service/auth";
import useCurrentUser from "@/hooks/useCurrentUser";

Expand Down
3 changes: 1 addition & 2 deletions web/components/FileStructure.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';
import {useEffect, useMemo} from "react";
import {Button, Group, Paper, Tree, TreeNodeData, useTree} from "@mantine/core";
import {Button, Group, Paper, Tree, useTree} from "@mantine/core";
import classes from "./FileStructure.module.scss";
import {FileStructureElement, FileStructureNewInput} from "@/components/FileStructureComponents";
import path from "node:path";
import {
buildDataFromStructure,
createFile,
Expand Down
1 change: 1 addition & 0 deletions web/components/FileStructureComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface FileStructureNewInputProps {

export const FileStructureNewInput = (props: FileStructureNewInputProps & GroupProps) => {

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {addFunc: _, label: _1, fileNames: _2, ...groupProps} = props;
const [isNew, setIsNew] = useState(false);
const form = useForm({
Expand Down
4 changes: 3 additions & 1 deletion web/components/FileStructureDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface FileStructureDisplayProps {
}

const flattenStructureToFiles = (structure: FileStructureTree): FileStructureFile[] => {
const files = structure.files;
const files = [...structure.files];
for (const folder of structure.folders ?? []) {
files.push(...flattenStructureToFiles(folder))
}
Expand Down Expand Up @@ -114,6 +114,8 @@ const FileStructureDisplay = ({structure, groupId, assignmentId, solutionId}: Fi
}
}, [loadAll, objectIds]);

//console.log(structure)

return (
<Grid>
<Grid.Col span={3}>
Expand Down
8 changes: 4 additions & 4 deletions web/components/InternalDropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Group, Paper, rem, SimpleGrid, Text} from "@mantine/core";
import {IconFile, IconUpload, IconX} from "@tabler/icons-react";
import { notifications } from '@mantine/notifications';

const ALLOWED_TEXT_EXTENSIONS = ['.java', '.go', '.kt', '.xml', '.md', '.gradle', '.properties', 'json', '.pem', '.yml', '.sql'];
const ALLOWED_TEXT_EXTENSIONS = ['.java', '.go', '.kt', '.xml', '.md', '.gradle', '.properties', 'json', '.pem', '.yml', '.sql', '.mod'];

interface InternalDropzoneProps {
files: FileWithPath[];
Expand All @@ -16,11 +16,11 @@ const InternalDropzone = ({files, setFiles}: InternalDropzoneProps) => {
return (
<>
<Dropzone
onDrop={setFiles}
onDrop={(f) => setFiles([...files, ...f])}
onReject={(f) => notifications.show({
title: 'Rejected files',
color: 'red',
message: `Rejected files: ${f.join(', ')}`
message: `Rejected files: ${f.map((file) => file.file.name).join(', ')}`
})}
maxSize={10 * 1024 ** 2}
accept={{'text/*': ALLOWED_TEXT_EXTENSIONS}}
Expand Down Expand Up @@ -57,7 +57,7 @@ const InternalDropzone = ({files, setFiles}: InternalDropzoneProps) => {
</Dropzone>
<SimpleGrid cols={{base: 1, sm: 4}} mt={10}>
{files.map((file) => (
<Paper radius="md" p="sm" withBorder><Text style={{overflow: 'hidden'}}>{file.name}</Text></Paper>
<Paper key={file.name} radius="md" p="sm" withBorder><Text style={{overflow: 'hidden'}}>{file.name}</Text></Paper>
))}
</SimpleGrid>
</>
Expand Down
12 changes: 8 additions & 4 deletions web/components/JobResultDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Job } from "@/service/types/tasky";
import {Highlight, Paper, TextInput} from "@mantine/core";
import {Highlight, Loader, Paper, TextInput} from "@mantine/core";
import { useState } from "react";

interface JobResultDisplayProps {
Expand All @@ -11,9 +11,13 @@ const JobResultDisplay = ({job}: JobResultDisplayProps) => {
return (
<Paper withBorder p={5}>
<TextInput mb={10} value={highlight} onChange={(e) => setHighlight(e.target.value as string)} />
<Highlight highlight={highlight} style={{whiteSpace: 'pre-line'}}>
{job.execution[0].error ?? job.execution[0].result ?? ""}
</Highlight>
{(job.execution[0].error ?? job.execution[0].result ?? "") === "" ? (
<Loader color="violet" type="dots" />
) : (
<Highlight highlight={highlight} style={{whiteSpace: 'pre-line'}}>
{job.execution[0].error ?? job.execution[0].result ?? ""}
</Highlight>
)}
</Paper>
);
}
Expand Down
1 change: 1 addition & 0 deletions web/components/SsrHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface SsrHeaderProps {
}

const SsrHeader: React.FC<SsrHeaderProps> = ({user}) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [s, _, removeSession] = useCookies(['session']);
const {setUser} = useCurrentUser();
const router = useRouter();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';
import { useState } from "react";
import FileStructure, {FileStructureTree} from "@/components/FileStructure";
import {Button, Group, Modal, Select, Title} from "@mantine/core";
import {Button, Group, Modal, Select, Title, TextInput} from "@mantine/core";
import {useSetState} from "@mantine/hooks";
import InternalDropzone from "@/components/InternalDropzone";
import {FileWithPath} from "@mantine/dropzone";
Expand Down Expand Up @@ -31,11 +31,13 @@ const AssignmentCreateOrUpdateCodeTestModal = ({onClose, groupId, assignmentId,
runner_cpu: cpuOptions[0],
runner_memory: memoryOptions[0],
runner_timeout: timeoutOptions[0],
runner_cmd: 'echo "Hello World!"'
},
validate: {
runner_cpu: (v) => cpuOptions.indexOf(v) === -1 ? 'Invalid CPU option' : null,
runner_memory: (v) => memoryOptions.indexOf(v) === -1 ? 'Invalid memory option' : null,
runner_timeout: (v) => timeoutOptions.indexOf(v) === -1 ? 'Invalid timeout option' : null
runner_timeout: (v) => timeoutOptions.indexOf(v) === -1 ? 'Invalid timeout option' : null,
runner_cmd: (v) => v.trim() === '' ? 'Please enter a execution cmd' : null
}
});

Expand Down Expand Up @@ -63,6 +65,7 @@ const AssignmentCreateOrUpdateCodeTestModal = ({onClose, groupId, assignmentId,
<Select key={form.key('runner_cpu')} label="CPU" {...form.getInputProps('runner_cpu')} data={cpuOptions}/>
<Select key={form.key('runner_memory')} label="Memory" {...form.getInputProps('runner_memory')} data={memoryOptions} />
<Select key={form.key('runner_timeout')} label="Timeout" {...form.getInputProps('runner_timeout')} data={timeoutOptions} />
<TextInput key={form.key('runner_cmd')} label="CMD" {...form.getInputProps('runner_cmd')} />
<Group mt={10}>
<Button type="submit">Create tests</Button>
<Button onClick={onClose} color="gray">Cancel</Button>
Expand Down
2 changes: 1 addition & 1 deletion web/components/assignments/CreateSolutionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Button, Group, List, Modal, Paper, Select, TextInput} from "@mantine/core";
import {Button, Group, List, Modal, Paper} from "@mantine/core";
import {Assignment} from "@/service/types/tasky";
import {FormEvent, useMemo, useState} from "react";
import {FileWithPath} from "@mantine/dropzone";
Expand Down
2 changes: 1 addition & 1 deletion web/components/form/RichTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RichTextEditor, Link, getTaskListExtension } from '@mantine/tiptap';
import {BubbleMenu, useEditor } from '@tiptap/react';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
Expand Down
1 change: 0 additions & 1 deletion web/components/solution/ExecutorUIDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Modal} from "@mantine/core";
import {useEffect, useMemo, useState} from "react";

export interface ExecutorUIDisplayProps {
jobId: string;
Expand Down
2 changes: 1 addition & 1 deletion web/service/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class ApiService {
if (resp.status !== 200) {
throw new ApiError(resp.status, obj.message);
}
return obj;
return obj as T;

} catch (e) {
if(e instanceof Error) {
Expand Down
Loading

0 comments on commit ca522aa

Please sign in to comment.