From c70cdbd26212d49b286aabfc29cf140b8464b180 Mon Sep 17 00:00:00 2001 From: plazmoid Date: Sat, 16 Jul 2022 18:11:51 +0500 Subject: [PATCH] big improvements (as usual) * removed api client macro * fixed passing --release mode in cargo make * optimized integration tests * added logging module (tracing) * allow u_panel to alter entries * reworked u_panel args (CRUD) * improved db hooks * started implementing web-interface ** incapsulated all frontend in binary ** setup workflow ** make u_panel accept commands from interface --- Cargo.toml | 1 - Makefile.toml | 45 +++-- bin/u_agent/Cargo.toml | 4 +- bin/u_agent/src/lib.rs | 34 ++-- bin/u_panel/Cargo.toml | 7 +- bin/u_panel/src/argparse.rs | 130 ++++++------- bin/u_panel/src/main.rs | 2 +- bin/u_panel/src/server/errors.rs | 17 ++ bin/u_panel/src/server/fe/.gitignore | 48 +++++ bin/u_panel/src/server/fe/angular.json | 2 + bin/u_panel/src/server/fe/package.json | 4 + .../src/server/fe/src/app/app.component.html | 50 ++++- .../src/server/fe/src/app/app.component.ts | 78 +++++++- .../src/server/fe/src/app/app.module.ts | 12 +- bin/u_panel/src/server/fe/src/index.html | 5 +- bin/u_panel/src/server/fe/src/styles.less | 3 + bin/u_panel/src/server/mod.rs | 46 ++++- bin/u_panel/src/tui/mod.rs | 2 +- bin/u_server/Cargo.toml | 3 +- bin/u_server/src/db.rs | 5 +- bin/u_server/src/handlers.rs | 89 +++++---- bin/u_server/src/init.rs | 136 ------------- bin/u_server/src/u_server.rs | 151 ++++++++++++++- .../integration-tests/tests_runner.Dockerfile | 2 +- integration/Cargo.toml | 3 +- integration/docker-compose.yml | 7 +- integration/integration_tests.sh | 1 + integration/tests/fixtures/agent.rs | 6 +- integration/tests/helpers/panel.rs | 2 +- lib/u_api_proc_macro/Cargo.toml | 16 -- lib/u_api_proc_macro/src/lib.rs | 181 ------------------ lib/u_api_proc_macro/tests/tests.rs | 15 -- lib/u_lib/Cargo.toml | 9 +- lib/u_lib/src/api.rs | 164 +++++++++------- lib/u_lib/src/builder.rs | 22 +-- lib/u_lib/src/config.rs | 6 +- lib/u_lib/src/errors/variants.rs | 11 +- lib/u_lib/src/lib.rs | 3 +- lib/u_lib/src/logging.rs | 16 ++ lib/u_lib/src/messaging/base.rs | 7 +- lib/u_lib/src/models/agent.rs | 7 +- lib/u_lib/src/models/jobs/assigned.rs | 4 +- lib/u_lib/src/models/jobs/meta.rs | 89 ++++++--- lib/u_lib/src/models/jobs/misc.rs | 3 +- lib/u_lib/src/utils/combined_result.rs | 8 +- scripts/deploy.sh | 2 +- scripts/gen_certs.sh | 1 + 47 files changed, 808 insertions(+), 651 deletions(-) create mode 100644 bin/u_panel/src/server/errors.rs create mode 100644 bin/u_panel/src/server/fe/.gitignore delete mode 100644 bin/u_server/src/init.rs delete mode 100644 lib/u_api_proc_macro/Cargo.toml delete mode 100644 lib/u_api_proc_macro/src/lib.rs delete mode 100644 lib/u_api_proc_macro/tests/tests.rs create mode 100644 lib/u_lib/src/logging.rs diff --git a/Cargo.toml b/Cargo.toml index a9f8f02..02c9d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "bin/u_run", "bin/u_server", "lib/u_lib", - "lib/u_api_proc_macro", "integration" ] diff --git a/Makefile.toml b/Makefile.toml index e70a9dc..c5fb8e0 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,3 +1,21 @@ +# i need to preserve --release in args, not to pass cargo make -p release +# due to cargo failing to parse "" argument +env_scripts = [''' +#!@duckscript +args = array ${1} ${2} ${3} ${4} ${5} ${6} ${7} +set_env PROFILE_OVERRIDE debug + +for arg in ${args} + e = eq ${arg} "--release" + if ${e} + set_env PROFILE_OVERRIDE release + end +end + +profile = get_env PROFILE_OVERRIDE +echo PROFILE_OVERRIDE=${profile} +'''] + [config] default_to_workspace = false @@ -11,6 +29,7 @@ PG_CONFIG_X86_64_UNKNOWN_LINUX_GNU = "${STATIC_PREFIX}/bin/pg_config" OPENSSL_STATIC = "true" OPENSSL_DIR = "${STATIC_PREFIX}" + [tasks.build_static_libs] script = "./scripts/build_musl_libs.sh" @@ -30,28 +49,21 @@ command = "${CARGO}" args = ["build", "--target", "${TARGET}", "${@}"] [tasks.release_tasks] +condition = { env = { "PROFILE_OVERRIDE" = "release"} } script = ''' -if [[ "${@}" =~ "--release" ]]; then - echo "Creating symlink to release dir..." - ln -s ${ROOTDIR}/target/${TARGET}/release ${ROOTDIR}/release || true - BINS=$(ls ./release/u_* -1 | grep -v ".d") - echo "Stripping..." - strip $BINS - echo "Packing..." - upx -9 $BINS -fi +BINS=$(ls ./target/${TARGET}/${PROFILE_OVERRIDE}/u_* -1 | grep -v ".d") +echo "Stripping..." +strip $BINS +echo "Packing..." +upx -9 $BINS ''' [tasks.build] dependencies = ["cargo_build", "release_tasks"] -command = "true" -args = [] +clear = true [tasks.run] -script = ''' -echo "wtf are you running? run binaries dud!" -exit 1 -''' +disabled = true [tasks.unit] command = "${CARGO}" @@ -59,6 +71,7 @@ args = ["test", "--target", "${TARGET}", "--lib", "--", "${@}"] [tasks.integration] script = ''' +echo "!!! This task doesn't perform project rebuild, trigger it manually if need" cd ./integration bash integration_tests.sh ${@} ''' @@ -78,4 +91,4 @@ docker run --rm \ dependencies = ["unit", "integration"] [tasks.deploy] -script = './scripts/deploy.sh' +script = './scripts/deploy.sh' \ No newline at end of file diff --git a/bin/u_agent/Cargo.toml b/bin/u_agent/Cargo.toml index 500fdb0..e06bc51 100644 --- a/bin/u_agent/Cargo.toml +++ b/bin/u_agent/Cargo.toml @@ -2,7 +2,7 @@ name = "u_agent" version = "0.1.0" authors = ["plazmoid "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,11 +10,11 @@ edition = "2018" tokio = { version = "1.2.0", features = ["macros", "rt-multi-thread", "process", "time"] } sysinfo = "0.10.5" log = "^0.4" -env_logger = "0.8.3" uuid = "0.6.5" reqwest = { version = "0.11", features = ["json"] } openssl = "*" u_lib = { version = "*", path = "../../lib/u_lib" } +daemonize = "0.4.1" [build-dependencies] openssl = "*" diff --git a/bin/u_agent/src/lib.rs b/bin/u_agent/src/lib.rs index 581fd7f..286b3ac 100644 --- a/bin/u_agent/src/lib.rs +++ b/bin/u_agent/src/lib.rs @@ -5,15 +5,15 @@ #[macro_use] extern crate log; -extern crate env_logger; +use daemonize::Daemonize; use std::panic; use std::sync::Arc; use tokio::time::{sleep, Duration}; use u_lib::{ - api::ClientHandler, builder::JobBuilder, cache::JobCache, errors::ErrChan, - executor::pop_completed, messaging::Reportable, models::AssignedJob, utils::load_env_default, - UError, UID, + api::ClientHandler, builder::JobBuilder, cache::JobCache, config::get_self_uid, + errors::ErrChan, executor::pop_completed, logging::init_logger, messaging::Reportable, + models::AssignedJob, utils::load_env_default, UError, }; const ITERATION_LATENCY: u64 = 5; @@ -70,10 +70,9 @@ async fn error_reporting(client: Arc) -> ! { async fn do_stuff(client: Arc) -> ! { loop { - match client.get_personal_jobs(Some(*UID)).await { + match client.get_personal_jobs(Some(get_self_uid())).await { Ok(resp) => { - let job_requests = resp.into_builtin_vec(); - process_request(job_requests, &client).await; + process_request(resp, &client).await; } Err(err) => ErrChan::send(err), } @@ -88,13 +87,26 @@ async fn do_stuff(client: Arc) -> ! { } pub async fn run_forever() -> ! { - //daemonize(); - env_logger::init(); - let env = load_env_default().unwrap(); - let client = Arc::new(ClientHandler::new(&env.u_server)); panic::set_hook(Box::new(|panic_info| { ErrChan::send(UError::Panic(panic_info.to_string())) })); + if cfg!(debug_assertions) { + init_logger(format!( + "u_agent-{}", + get_self_uid() + .hyphenated() + .to_string() + .split("-") + .next() + .unwrap() + )); + } else { + if let Err(e) = Daemonize::new().start() { + ErrChan::send(UError::Runtime(e.to_string())) + } + } + let env = load_env_default().unwrap(); + let client = Arc::new(ClientHandler::new(&env.u_server, None)); tokio::spawn(error_reporting(client.clone())); do_stuff(client).await } diff --git a/bin/u_panel/Cargo.toml b/bin/u_panel/Cargo.toml index 6facac5..21456d0 100644 --- a/bin/u_panel/Cargo.toml +++ b/bin/u_panel/Cargo.toml @@ -2,7 +2,7 @@ name = "u_panel" version = "0.1.0" authors = ["plazmoid "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,12 +11,11 @@ actix-web = "3.3.2" backtrace = "0.3.61" structopt = "0.3.21" log = "^0.4" -env_logger = "0.7.1" uuid = "0.6.5" serde_json = "1.0.4" serde = { version = "1.0.114", features = ["derive"] } tokio = { version = "1.11.0", features = ["rt", "rt-multi-thread"] } -u_lib = { version = "*", path = "../../lib/u_lib" } +u_lib = { version = "*", path = "../../lib/u_lib", features = ["panel"] } tui = { version = "0.16", default-features = false, features = ['crossterm'] } crossterm = "0.22.1" anyhow = "1.0.44" @@ -30,3 +29,5 @@ signal-hook = "0.3.12" tracing-appender = "0.2.0" rust-embed = { version = "6.3.0", features = ["debug-embed", "compression"] } mime_guess = "2.0.4" +shlex = "1.1.0" +thiserror = "1.0.31" diff --git a/bin/u_panel/src/argparse.rs b/bin/u_panel/src/argparse.rs index c9f8c0f..371cb77 100644 --- a/bin/u_panel/src/argparse.rs +++ b/bin/u_panel/src/argparse.rs @@ -1,7 +1,10 @@ -use std::fmt; use structopt::StructOpt; use u_lib::{ - api::ClientHandler, datatypes::PanelResult, messaging::AsMsg, models::JobMeta, UError, UResult, + api::ClientHandler, + datatypes::PanelResult, + messaging::AsMsg, + models::{Agent, AssignedJob, JobMeta, RawJobMeta}, + UError, UResult, }; use uuid::Uuid; @@ -9,15 +12,13 @@ use uuid::Uuid; pub struct Args { #[structopt(subcommand)] cmd: Cmd, - #[structopt(long)] - json: bool, } #[derive(StructOpt, Debug)] enum Cmd { - Agents(LD), - Jobs(JobALD), - Map(JobMapALD), + Agents(RUD), + Jobs(JobCRUD), + Map(JobMapCRUD), //TUI(TUIArgs), Serve, } @@ -29,18 +30,12 @@ pub struct TUIArgs { } #[derive(StructOpt, Debug)] -enum JobALD { - Add { - //#[structopt(long, parse(try_from_str = parse_uuid))] - //agent: Option, - #[structopt(long)] - alias: String, - - #[structopt(subcommand)] - cmd: JobCmd, +enum JobCRUD { + Create { + job: String, }, #[structopt(flatten)] - LD(LD), + RUD(RUD), } #[derive(StructOpt, Debug)] @@ -50,29 +45,26 @@ enum JobCmd { } #[derive(StructOpt, Debug)] -enum JobMapALD { - Add { +enum JobMapCRUD { + Create { #[structopt(parse(try_from_str = parse_uuid))] agent_uid: Uuid, job_idents: Vec, }, - List { - #[structopt(parse(try_from_str = parse_uuid))] - uid: Option, - }, - Delete { - #[structopt(parse(try_from_str = parse_uuid))] - uid: Uuid, - }, + #[structopt(flatten)] + RUD(RUD), } #[derive(StructOpt, Debug)] -enum LD { - List { +enum RUD { + Read { #[structopt(parse(try_from_str = parse_uuid))] uid: Option, }, + Update { + item: String, + }, Delete { #[structopt(parse(try_from_str = parse_uuid))] uid: Uuid, @@ -83,61 +75,55 @@ fn parse_uuid(src: &str) -> Result { Uuid::parse_str(src).map_err(|e| e.to_string()) } -pub async fn process_cmd(client: ClientHandler, args: Args) -> UResult<()> { - struct Printer { - json: bool, - } - - impl Printer { - pub fn print(&self, data: UResult) { - if self.json { - let data = match data { - Ok(r) => PanelResult::Ok(r), - Err(e) => PanelResult::Err(e), - }; - println!("{}", serde_json::to_string_pretty(&data).unwrap()); - } else { - match data { - Ok(r) => println!("{}", r), - Err(e) => eprintln!("Error: {}", e), - } - } - } +pub async fn process_cmd(client: ClientHandler, args: Args) -> UResult { + fn to_json(data: UResult) -> String { + let data = match data { + Ok(r) => PanelResult::Ok(r), + Err(e) => PanelResult::Err(e), + }; + serde_json::to_string(&data).unwrap() } - let printer = Printer { json: args.json }; - match args.cmd { + Ok(match args.cmd { Cmd::Agents(action) => match action { - LD::List { uid } => printer.print(client.get_agents(uid).await), - LD::Delete { uid } => printer.print(client.del(Some(uid)).await), + RUD::Read { uid } => to_json(client.get_agents(uid).await), + RUD::Update { item } => { + let agent = serde_json::from_str::(&item)?; + to_json(client.update_item(agent).await) + } + RUD::Delete { uid } => to_json(client.del(uid).await), }, Cmd::Jobs(action) => match action { - JobALD::Add { - cmd: JobCmd::Cmd(cmd), - alias, - .. - } => { - let job = JobMeta::builder() - .with_shell(cmd.join(" ")) - .with_alias(alias) - .build()?; - printer.print(client.upload_jobs(&[job]).await); + JobCRUD::Create { job } => { + let raw_job = serde_json::from_str::(&job)?; + let job = raw_job.into_builder().build()?; + to_json(client.upload_jobs(&[job]).await) + } + JobCRUD::RUD(RUD::Read { uid }) => to_json(client.get_jobs(uid).await), + JobCRUD::RUD(RUD::Update { item }) => { + let job = serde_json::from_str::(&item)?; + to_json(client.update_item(job).await) } - JobALD::LD(LD::List { uid }) => printer.print(client.get_jobs(uid).await), - JobALD::LD(LD::Delete { uid }) => printer.print(client.del(Some(uid)).await), + JobCRUD::RUD(RUD::Delete { uid }) => to_json(client.del(uid).await), }, Cmd::Map(action) => match action { - JobMapALD::Add { + JobMapCRUD::Create { agent_uid, job_idents, - } => printer.print(client.set_jobs(Some(agent_uid), &job_idents).await), - JobMapALD::List { uid } => printer.print(client.get_agent_jobs(uid).await), - JobMapALD::Delete { uid } => printer.print(client.del(Some(uid)).await), + } => to_json(client.set_jobs(agent_uid, &job_idents).await), + JobMapCRUD::RUD(RUD::Read { uid }) => to_json(client.get_agent_jobs(uid).await), + JobMapCRUD::RUD(RUD::Update { item }) => { + let assigned = serde_json::from_str::(&item)?; + to_json(client.update_item(assigned).await) + } + JobMapCRUD::RUD(RUD::Delete { uid }) => to_json(client.del(uid).await), }, /*Cmd::TUI(args) => crate::tui::init_tui(&args) .await .map_err(|e| UError::PanelError(e.to_string()))?,*/ - Cmd::Serve => crate::server::serve().map_err(|e| UError::PanelError(e.to_string()))?, - } - Ok(()) + Cmd::Serve => { + crate::server::serve(client).map_err(|e| UError::PanelError(e.to_string()))?; + String::new() + } + }) } diff --git a/bin/u_panel/src/main.rs b/bin/u_panel/src/main.rs index 2f0d685..413a0b7 100644 --- a/bin/u_panel/src/main.rs +++ b/bin/u_panel/src/main.rs @@ -22,7 +22,7 @@ struct AccessEnv { #[tokio::main] async fn main() -> AnyResult<()> { let env = load_env::()?; - let client = ClientHandler::new(&env.u_server).password(env.admin_auth_token); + let client = ClientHandler::new(&env.u_server, Some(env.admin_auth_token)); let args = Args::from_args(); process_cmd(client, args).await?; diff --git a/bin/u_panel/src/server/errors.rs b/bin/u_panel/src/server/errors.rs new file mode 100644 index 0000000..003ff2f --- /dev/null +++ b/bin/u_panel/src/server/errors.rs @@ -0,0 +1,17 @@ +use actix_web::http::StatusCode; +use actix_web::ResponseError; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Arg parse error: {0}")] + ArgparseError(#[from] structopt::clap::Error), + + #[error("Just an error: {0}")] + JustError(String), +} + +impl ResponseError for Error { + fn status_code(&self) -> actix_web::http::StatusCode { + StatusCode::BAD_REQUEST + } +} diff --git a/bin/u_panel/src/server/fe/.gitignore b/bin/u_panel/src/server/fe/.gitignore new file mode 100644 index 0000000..e5c1ba1 --- /dev/null +++ b/bin/u_panel/src/server/fe/.gitignore @@ -0,0 +1,48 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +package-lock.json \ No newline at end of file diff --git a/bin/u_panel/src/server/fe/angular.json b/bin/u_panel/src/server/fe/angular.json index 5bb4463..3972326 100644 --- a/bin/u_panel/src/server/fe/angular.json +++ b/bin/u_panel/src/server/fe/angular.json @@ -31,6 +31,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", "src/styles.less" ], "scripts": [] @@ -99,6 +100,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", "src/styles.less" ], "scripts": [] diff --git a/bin/u_panel/src/server/fe/package.json b/bin/u_panel/src/server/fe/package.json index ae1f9de..9cc9000 100644 --- a/bin/u_panel/src/server/fe/package.json +++ b/bin/u_panel/src/server/fe/package.json @@ -11,15 +11,19 @@ "private": true, "dependencies": { "@angular/animations": "~13.1.0", + "@angular/cdk": "^13.3.9", "@angular/common": "~13.1.0", "@angular/compiler": "~13.1.0", "@angular/core": "~13.1.0", "@angular/forms": "~13.1.0", + "@angular/material": "^13.3.9", "@angular/platform-browser": "~13.1.0", "@angular/platform-browser-dynamic": "~13.1.0", "@angular/router": "~13.1.0", + "@types/uuid": "^8.3.4", "rxjs": "~7.4.0", "tslib": "^2.3.0", + "uuid": "^8.3.2", "zone.js": "~0.11.4" }, "devDependencies": { diff --git a/bin/u_panel/src/server/fe/src/app/app.component.html b/bin/u_panel/src/server/fe/src/app/app.component.html index 171ac03..a323767 100644 --- a/bin/u_panel/src/server/fe/src/app/app.component.html +++ b/bin/u_panel/src/server/fe/src/app/app.component.html @@ -1 +1,49 @@ -{{ title }} \ No newline at end of file + + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
id{{row.id}}Alias{{row.alias}}user@hostname{{row.username}}@{{row.hostname}} + Last active + {{row.last_active}}
+ +
+ + +
+ +
+ + +
\ No newline at end of file diff --git a/bin/u_panel/src/server/fe/src/app/app.component.ts b/bin/u_panel/src/server/fe/src/app/app.component.ts index 25a2bce..76deda5 100644 --- a/bin/u_panel/src/server/fe/src/app/app.component.ts +++ b/bin/u_panel/src/server/fe/src/app/app.component.ts @@ -1,10 +1,82 @@ -import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Component, ViewChild, AfterViewInit } from '@angular/core'; +import { timer, Observable, of as observableOf } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; + +interface Agent { + alias: string | null, + hostname: string, + id: string, + is_root: boolean, + is_root_allowed: boolean, + last_active: Date, + platform: string, + regtime: Date, + state: "new" | "active" | "banned", + token: string | null, + username: string, +} @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.less'] }) -export class AppComponent { - title = 'ты лох'; +export class AppComponent implements AfterViewInit { + displayedColumns: string[] = ['id', 'alias', 'username', 'last_active']; + exampleDatabase!: ExampleHttpDatabase | null; + + table_data: Agent[] = []; + isLoadingResults = true; + + constructor(private _httpClient: HttpClient) { } + + ngAfterViewInit() { + this.exampleDatabase = new ExampleHttpDatabase(this._httpClient); + this.fetch_agents(); + // If the user changes the sort order, reset back to the first page. + //this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + } + + fetch_agents() { + timer(0) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.exampleDatabase!.getAgents().pipe(catchError(() => observableOf(null))); + }), + map(data => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + + if (data === null) { + return []; + } + + // Only refresh the result length if there is new data. In case of rate + // limit errors, we do not want to reset the paginator to zero, as that + // would prevent users from re-triggering requests. + return data.data; + }), + ) + .subscribe(data => { if (typeof data !== 'string') { this.table_data = data } else { alert(`Error: ${data}`) } }); + } +} + +interface ServerResponse { + status: "ok" | "err", + data: T | string +} + +class ExampleHttpDatabase { + constructor(private _httpClient: HttpClient) { } + + getAgents(): Observable> { + const requestUrl = "/cmd/"; + const cmd = "agents list"; + + return this._httpClient.post>(requestUrl, cmd); + } } diff --git a/bin/u_panel/src/server/fe/src/app/app.module.ts b/bin/u_panel/src/server/fe/src/app/app.module.ts index b1c6c96..56e6d5c 100644 --- a/bin/u_panel/src/server/fe/src/app/app.module.ts +++ b/bin/u_panel/src/server/fe/src/app/app.module.ts @@ -3,6 +3,11 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatTableModule } from '@angular/material/table'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ @@ -10,7 +15,12 @@ import { AppComponent } from './app.component'; ], imports: [ BrowserModule, - AppRoutingModule + HttpClientModule, + AppRoutingModule, + MatTabsModule, + MatTableModule, + MatProgressSpinnerModule, + BrowserAnimationsModule ], providers: [], bootstrap: [AppComponent] diff --git a/bin/u_panel/src/server/fe/src/index.html b/bin/u_panel/src/server/fe/src/index.html index 04af3e3..94a6658 100644 --- a/bin/u_panel/src/server/fe/src/index.html +++ b/bin/u_panel/src/server/fe/src/index.html @@ -6,8 +6,11 @@ + + + - + diff --git a/bin/u_panel/src/server/fe/src/styles.less b/bin/u_panel/src/server/fe/src/styles.less index 90d4ee0..7e7239a 100644 --- a/bin/u_panel/src/server/fe/src/styles.less +++ b/bin/u_panel/src/server/fe/src/styles.less @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/bin/u_panel/src/server/mod.rs b/bin/u_panel/src/server/mod.rs index aaebf5a..f5a5c30 100644 --- a/bin/u_panel/src/server/mod.rs +++ b/bin/u_panel/src/server/mod.rs @@ -11,10 +11,15 @@ almost all fields are editable, rows are deletable */ -use actix_web::{get, web, App, HttpResponse, HttpServer, Responder}; +mod errors; + +use crate::{process_cmd, Args}; +use actix_web::{get, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder}; +use errors::Error; use rust_embed::RustEmbed; use std::borrow::Cow; -use u_lib::unwrap_enum; +use structopt::StructOpt; +use u_lib::{api::ClientHandler, logging::init_logger, unwrap_enum}; #[derive(RustEmbed)] #[folder = "./src/server/fe/dist/fe/"] @@ -45,12 +50,37 @@ async fn static_files_adapter(path: web::Path<(String,)>) -> impl Responder { } } +#[post("/cmd/")] +async fn send_cmd( + cmd: web::Json, + client: web::Data, +) -> Result { + let parsed_cmd = Args::from_iter_safe( + shlex::split(&cmd.into_inner()).ok_or(Error::JustError("shlex failed".to_string()))?, + )?; + Ok( + match process_cmd(client.as_ref().clone(), parsed_cmd).await { + Ok(r) => HttpResponse::Ok().body(r), + Err(e) => HttpResponse::BadRequest().body(e.to_string()), + }, + ) +} + #[actix_web::main] -pub async fn serve() -> std::io::Result<()> { +pub async fn serve(client: ClientHandler) -> std::io::Result<()> { + init_logger("u_panel"); + let addr = "127.0.0.1:8080"; - println!("Serving at http://{}", addr); - HttpServer::new(|| App::new().service(main_page).service(static_files_adapter)) - .bind(addr)? - .run() - .await + info!("Serving at http://{}", addr); + HttpServer::new(move || { + App::new() + .wrap(Logger::default()) + .app_data(web::Data::new(client.clone())) + .service(main_page) + .service(send_cmd) + .service(static_files_adapter) + }) + .bind(addr)? + .run() + .await } diff --git a/bin/u_panel/src/tui/mod.rs b/bin/u_panel/src/tui/mod.rs index 80b0a08..3a86c84 100644 --- a/bin/u_panel/src/tui/mod.rs +++ b/bin/u_panel/src/tui/mod.rs @@ -148,7 +148,7 @@ fn init_signal_handlers(gui: bool) { fn init_logger() { use tracing_appender::rolling::{RollingFileAppender, Rotation}; - use tracing_subscriber::EnvFilter; + use tracing_subscriber::EnvFlter; if env::var("RUST_LOG").is_err() { env::set_var("RUST_LOG", "info") diff --git a/bin/u_server/Cargo.toml b/bin/u_server/Cargo.toml index 13d12ca..41e4693 100644 --- a/bin/u_server/Cargo.toml +++ b/bin/u_server/Cargo.toml @@ -1,12 +1,11 @@ [package] authors = ["plazmoid "] -edition = "2018" +edition = "2021" name = "u_server" version = "0.1.0" [dependencies] log = "0.4.11" -simplelog = "0.10" thiserror = "*" warp = { version = "0.3.1", features = ["tls"] } uuid = { version = "0.6.5", features = ["serde", "v4"] } diff --git a/bin/u_server/src/db.rs b/bin/u_server/src/db.rs index db064ec..2c77133 100644 --- a/bin/u_server/src/db.rs +++ b/bin/u_server/src/db.rs @@ -31,8 +31,9 @@ impl UDB { "postgres://{}:{}@{}/{}", env.db_user, env.db_password, env.db_host, env.db_name ); - let conn = PgConnection::establish(&db_url).unwrap(); - let instance = UDB { conn }; + let instance = UDB { + conn: PgConnection::establish(&db_url).unwrap(), + }; Arc::new(Mutex::new(instance)) }) .lock() diff --git a/bin/u_server/src/handlers.rs b/bin/u_server/src/handlers.rs index d1573cc..efcb78a 100644 --- a/bin/u_server/src/handlers.rs +++ b/bin/u_server/src/handlers.rs @@ -7,50 +7,32 @@ use u_lib::{ utils::{OneOrVec, Stripped}, }; use uuid::Uuid; -use warp::{ - http::{Response, StatusCode}, - Rejection, Reply, -}; -/* -pub fn build_response(code: StatusCode, body: impl Into) -> Response { - Response::builder().status(code).body(body.into()).unwrap() -} - -pub fn build_ok(body: impl Into) -> Response { - build_response(StatusCode::OK, body) -} +use warp::Rejection; -pub fn build_err(body: impl ToString) -> Response { - build_response(StatusCode::BAD_REQUEST, body.to_string()) -} - -pub fn build_message(m: M) -> Response { - warp::reply::json(&m.as_message()).into_response() -} -*/ +type EndpResult = Result; pub struct Endpoints; impl Endpoints { - pub async fn add_agent(msg: Agent) -> Result<(), Rejection> { + pub async fn add_agent(msg: Agent) -> EndpResult<()> { UDB::lock_db().insert_agent(&msg).map_err(From::from) } - pub async fn get_agents(uid: Option) -> Result, Rejection> { + pub async fn get_agents(uid: Option) -> EndpResult> { UDB::lock_db().get_agents(uid).map_err(From::from) } - pub async fn get_jobs(uid: Option) -> Result, Rejection> { + pub async fn get_jobs(uid: Option) -> EndpResult> { UDB::lock_db().get_jobs(uid).map_err(From::from) } - pub async fn get_agent_jobs(uid: Option) -> Result, Rejection> { + pub async fn get_agent_jobs(uid: Option) -> EndpResult> { UDB::lock_db() .get_exact_jobs(uid, false) .map_err(From::from) } - pub async fn get_personal_jobs(uid: Option) -> Result, Rejection> { + pub async fn get_personal_jobs(uid: Option) -> EndpResult> { let agents = UDB::lock_db().get_agents(uid)?; if agents.is_empty() { let db = UDB::lock_db(); @@ -58,30 +40,26 @@ impl Endpoints { let job = db.find_job_by_alias("agent_hello")?; db.set_jobs_for_agent(&uid.unwrap(), &[job.id])?; } - let result = UDB::lock_db().get_exact_jobs(uid, true); - match result { - Ok(r) => { - let db = UDB::lock_db(); - for j in r.iter() { - db.update_job_status(j.id, JobState::Running)?; - } - Ok(r) - } - Err(e) => Err(e.into()), + let result = UDB::lock_db().get_exact_jobs(uid, true)?; + + let db = UDB::lock_db(); + for j in result.iter() { + db.update_job_status(j.id, JobState::Running)?; } + Ok(result) } - pub async fn upload_jobs(msg: BaseMessage<'static, Vec>) -> Result<(), Rejection> { + pub async fn upload_jobs(msg: BaseMessage<'static, Vec>) -> EndpResult<()> { UDB::lock_db() .insert_jobs(&msg.into_inner()) .map_err(From::from) } - pub async fn del(uid: Uuid) -> Result { + pub async fn del(uid: Uuid) -> EndpResult { let db = UDB::lock_db(); let del_fns = &[UDB::del_agents, UDB::del_jobs, UDB::del_results]; for del_fn in del_fns { - let affected = del_fn(&db, &[uid]).unwrap(); + let affected = del_fn(&db, &[uid])?; if affected > 0 { return Ok(affected); } @@ -92,7 +70,7 @@ impl Endpoints { pub async fn set_jobs( agent_uid: Uuid, msg: BaseMessage<'static, Vec>, - ) -> Result, Rejection> { + ) -> EndpResult> { msg.into_inner() .into_iter() .map(|ident| { @@ -106,7 +84,7 @@ impl Endpoints { pub async fn report + AsMsg + 'static>( msg: BaseMessage<'static, Data>, - ) -> Result<(), Rejection> { + ) -> EndpResult<()> { let id = msg.id; let mut failed = vec![]; for entry in msg.into_inner().into_vec() { @@ -134,7 +112,7 @@ impl Endpoints { err.agent_id, Stripped(&err.msg.as_str()) ); - UDB::lock_db().report_error(&err).unwrap(); + UDB::lock_db().report_error(&err)?; } Reportable::Dummy => (), } @@ -144,4 +122,33 @@ impl Endpoints { } Ok(()) } + + pub async fn update_agent(agent: BaseMessage<'static, Agent>) -> EndpResult<()> { + agent + .into_inner() + .save_changes::(&UDB::lock_db().conn) + .map_err(Error::from)?; + Ok(()) + } + + pub async fn update_job(job: BaseMessage<'static, JobMeta>) -> EndpResult<()> { + job.into_inner() + .save_changes::(&UDB::lock_db().conn) + .map_err(Error::from)?; + Ok(()) + } + + pub async fn update_assigned_job( + assigned: BaseMessage<'static, AssignedJob>, + ) -> EndpResult<()> { + assigned + .into_inner() + .save_changes::(&UDB::lock_db().conn) + .map_err(Error::from)?; + Ok(()) + } + + pub async fn download(_file_uid: String) -> EndpResult> { + todo!() + } } diff --git a/bin/u_server/src/init.rs b/bin/u_server/src/init.rs deleted file mode 100644 index 8127227..0000000 --- a/bin/u_server/src/init.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::handlers::Endpoints; -use crate::{db::UDB, errors::SResult}; -use serde::de::DeserializeOwned; -use std::path::PathBuf; -use u_lib::{ - messaging::{AsMsg, BaseMessage, Reportable}, - models::*, -}; -use uuid::Uuid; -use warp::{ - body, - reply::{json, reply, Json}, - Filter, Rejection, Reply, -}; - -fn get_content() -> impl Filter,), Error = Rejection> + Clone -where - M: AsMsg + Sync + Send + DeserializeOwned + 'static, -{ - body::content_length_limit(1024 * 64).and(body::json::>()) -} - -fn into_message(msg: M) -> Json { - json(&msg.as_message()) -} - -pub fn init_filters( - auth_token: &str, -) -> impl Filter + Clone { - let infallible_none = |_| async { Ok::<(Option,), std::convert::Infallible>((None,)) }; - - let get_agents = warp::get() - .and(warp::path("get_agents")) - .and( - warp::path::param::() - .map(Some) - .or_else(infallible_none), - ) - .and_then(Endpoints::get_agents) - .map(into_message); - - let upload_jobs = warp::post() - .and(warp::path("upload_jobs")) - .and(get_content::>()) - .and_then(Endpoints::upload_jobs) - .map(|_| reply()); - - let get_jobs = warp::get() - .and(warp::path("get_jobs")) - .and( - warp::path::param::() - .map(Some) - .or_else(infallible_none), - ) - .and_then(Endpoints::get_jobs) - .map(into_message); - - let get_agent_jobs = warp::get() - .and(warp::path("get_agent_jobs")) - .and( - warp::path::param::() - .map(Some) - .or_else(infallible_none), - ) - .and_then(Endpoints::get_agent_jobs) - .map(into_message); - - let get_personal_jobs = warp::get() - .and(warp::path("get_personal_jobs")) - .and(warp::path::param::().map(Some)) - .and_then(Endpoints::get_personal_jobs) - .map(into_message); - - let del = warp::get() - .and(warp::path("del")) - .and(warp::path::param::()) - .and_then(Endpoints::del) - .map(|_| reply()); - - let set_jobs = warp::post() - .and(warp::path("set_jobs")) - .and(warp::path::param::()) - .and(get_content::>()) - .and_then(Endpoints::set_jobs) - .map(into_message); - - let report = warp::post() - .and(warp::path("report")) - .and(get_content::>()) - .and_then(Endpoints::report) - .map(|_| reply()); - - let auth_token = format!("Bearer {auth_token}",).into_boxed_str(); - let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); - - let auth_zone = (get_agents - .or(get_jobs) - .or(upload_jobs) - .or(del) - .or(set_jobs) - .or(get_agent_jobs)) - .and(auth_header); - - let agent_zone = get_jobs.clone().or(get_personal_jobs).or(report); - - auth_zone.or(agent_zone) -} - -pub fn prefill_jobs() -> SResult<()> { - let agent_hello = JobMeta::builder() - .with_type(misc::JobType::Manage) - .with_alias("agent_hello") - .build() - .unwrap(); - UDB::lock_db().insert_jobs(&[agent_hello]) -} - -pub fn init_logger() { - use simplelog::*; - use std::fs::OpenOptions; - let log_cfg = ConfigBuilder::new() - .set_time_format_str("%x %X") - .set_time_to_local(true) - .build(); - let logfile = OpenOptions::new() - .append(true) - .create(true) - .open(PathBuf::from("logs").join("u_server.log")) - .unwrap(); - let level = LevelFilter::Info; - let loggers = vec![ - WriteLogger::new(level, log_cfg.clone(), logfile) as Box, - TermLogger::new(level, log_cfg, TerminalMode::Stderr, ColorChoice::Auto), - ]; - CombinedLogger::init(loggers).unwrap(); -} diff --git a/bin/u_server/src/u_server.rs b/bin/u_server/src/u_server.rs index 1483acd..d7ec8b3 100644 --- a/bin/u_server/src/u_server.rs +++ b/bin/u_server/src/u_server.rs @@ -14,28 +14,159 @@ extern crate diesel; mod db; mod errors; mod handlers; -mod init; use errors::{Error, SResult}; -use init::*; -use serde::Deserialize; +use serde::{de::DeserializeOwned, Deserialize}; use std::path::PathBuf; -use u_lib::{config::MASTER_PORT, utils::load_env}; -use warp::Filter; +use u_lib::{ + config::MASTER_PORT, + logging::init_logger, + messaging::{AsMsg, BaseMessage, Reportable}, + models::*, + utils::load_env, +}; +use uuid::Uuid; +use warp::{ + body, + reply::{json, reply, Json}, + Filter, Rejection, Reply, +}; + +use crate::db::UDB; +use crate::handlers::Endpoints; #[derive(Deserialize)] struct ServEnv { admin_auth_token: String, } -//TODO: tracing-subscriber +fn get_content() -> impl Filter,), Error = Rejection> + Clone +where + M: AsMsg + Sync + Send + DeserializeOwned + 'static, +{ + body::content_length_limit(1024 * 64).and(body::json::>()) +} + +fn into_message(msg: M) -> Json { + json(&msg.as_message()) +} + +pub fn init_endpoints( + auth_token: &str, +) -> impl Filter + Clone { + let path = |p: &'static str| warp::post().and(warp::path(p)); + let infallible_none = |_| async { Ok::<(Option,), std::convert::Infallible>((None,)) }; + + let get_agents = path("get_agents") + .and( + warp::path::param::() + .map(Some) + .or_else(infallible_none), + ) + .and_then(Endpoints::get_agents) + .map(into_message); + + let upload_jobs = path("upload_jobs") + .and(get_content::>()) + .and_then(Endpoints::upload_jobs) + .map(ok); + + let get_jobs = path("get_jobs") + .and( + warp::path::param::() + .map(Some) + .or_else(infallible_none), + ) + .and_then(Endpoints::get_jobs) + .map(into_message); + + let get_agent_jobs = path("get_agent_jobs") + .and( + warp::path::param::() + .map(Some) + .or_else(infallible_none), + ) + .and_then(Endpoints::get_agent_jobs) + .map(into_message); + + let get_personal_jobs = path("get_personal_jobs") + .and(warp::path::param::().map(Some)) + .and_then(Endpoints::get_personal_jobs) + .map(into_message); + + let del = path("del") + .and(warp::path::param::()) + .and_then(Endpoints::del) + .map(ok); + + let set_jobs = path("set_jobs") + .and(warp::path::param::()) + .and(get_content::>()) + .and_then(Endpoints::set_jobs) + .map(into_message); + + let report = path("report") + .and(get_content::>()) + .and_then(Endpoints::report) + .map(ok); + + let update_agent = path("update_item") + .and(get_content::()) + .and_then(Endpoints::update_agent) + .map(ok); + + let update_job = path("update_item") + .and(get_content::()) + .and_then(Endpoints::update_job) + .map(ok); + + let update_assigned_job = path("update_item") + .and(get_content::()) + .and_then(Endpoints::update_assigned_job) + .map(ok); + + let download = path("download") + .and(warp::path::param::()) + .and_then(Endpoints::download) + .map(ok); + + let auth_token = format!("Bearer {auth_token}",).into_boxed_str(); + let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); + + let auth_zone = (get_agents + .or(get_jobs) + .or(upload_jobs) + .or(del) + .or(set_jobs) + .or(get_agent_jobs) + .or(update_agent) + .or(update_job) + .or(update_assigned_job) + .or(download)) + .and(auth_header); + + let agent_zone = get_jobs.or(get_personal_jobs).or(report).or(download); + + auth_zone.or(agent_zone) +} + +pub fn prefill_jobs() -> SResult<()> { + let agent_hello = RawJobMeta::builder() + .with_type(misc::JobType::Manage) + .with_alias("agent_hello") + .build() + .unwrap(); + UDB::lock_db().insert_jobs(&[agent_hello]) +} + pub async fn serve() -> SResult<()> { - init_logger(); + init_logger("u_server"); prefill_jobs()?; let env = load_env::().map_err(|e| Error::Other(e.to_string()))?; - let routes = init_filters(&env.admin_auth_token); + let routes = init_endpoints(&env.admin_auth_token); let certs_dir = PathBuf::from("certs"); + warp::serve(routes.with(warp::log("warp"))) .tls() .cert_path(certs_dir.join("server.crt")) @@ -46,6 +177,10 @@ pub async fn serve() -> SResult<()> { Ok(()) } +fn ok(_: T) -> impl Reply { + reply() +} + /* #[cfg(test)] mod tests { diff --git a/images/integration-tests/tests_runner.Dockerfile b/images/integration-tests/tests_runner.Dockerfile index 0a60d7a..4714c7d 100644 --- a/images/integration-tests/tests_runner.Dockerfile +++ b/images/integration-tests/tests_runner.Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.60 +FROM rust:1.62 RUN rustup target add x86_64-unknown-linux-musl CMD ["sleep", "3600"] \ No newline at end of file diff --git a/integration/Cargo.toml b/integration/Cargo.toml index a4ae852..5e3f6fd 100644 --- a/integration/Cargo.toml +++ b/integration/Cargo.toml @@ -2,14 +2,13 @@ name = "integration" version = "0.1.0" authors = ["plazmoid "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio = { version = "1.2.0", features = ["macros", "rt-multi-thread", "process", "time"] } log = "^0.4" -env_logger = "0.8.3" uuid = { version = "0.6.5", features = ["serde", "v4"] } reqwest = { version = "0.11", features = ["json"] } serde_json = "1.0" diff --git a/integration/docker-compose.yml b/integration/docker-compose.yml index 40122f3..bca0fdd 100644 --- a/integration/docker-compose.yml +++ b/integration/docker-compose.yml @@ -11,7 +11,7 @@ services: networks: - u_net volumes: - - ../release/u_server:/unki/u_server + - ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_server:/unki/u_server - ../certs:/unki/certs - ../logs:/unki/logs:rw working_dir: /unki @@ -58,7 +58,7 @@ services: networks: - u_net volumes: - - ../release/u_agent:/u_agent + - ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_agent:/u_agent command: /u_agent u_server env_file: - ../.env @@ -76,9 +76,8 @@ services: volumes: - ./:/tests/ - ../certs:/certs - - ../release/u_panel:/u_panel + - ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_panel:/u_panel - ../lib/u_lib:/lib/u_lib - - ../lib/u_api_proc_macro:/lib/u_api_proc_macro working_dir: /tests/ depends_on: diff --git a/integration/integration_tests.sh b/integration/integration_tests.sh index 97d06e8..b8efc7e 100755 --- a/integration/integration_tests.sh +++ b/integration/integration_tests.sh @@ -2,4 +2,5 @@ set -e export DOCKER_UID=$(id -u) export DOCKER_GID=$(id -g) +[[ "$@" =~ "--release" ]] && export PROFILE=release || export PROFILE=debug python integration_tests.py $@ diff --git a/integration/tests/fixtures/agent.rs b/integration/tests/fixtures/agent.rs index 5188830..4160af7 100644 --- a/integration/tests/fixtures/agent.rs +++ b/integration/tests/fixtures/agent.rs @@ -8,14 +8,14 @@ pub struct RegisteredAgent { impl RegisteredAgent { pub async fn unregister(self) { - let cli = ClientHandler::new(&ENV.u_server); - cli.del(Some(self.uid)).await.unwrap(); + let cli = ClientHandler::new(&ENV.u_server, None); + cli.del(self.uid).await.unwrap(); } } #[fixture] pub async fn register_agent() -> RegisteredAgent { - let cli = ClientHandler::new(&ENV.u_server); + let cli = ClientHandler::new(&ENV.u_server, None); let agent_uid = Uuid::new_v4(); let resp = cli .get_personal_jobs(Some(agent_uid)) diff --git a/integration/tests/helpers/panel.rs b/integration/tests/helpers/panel.rs index 27d2639..526b028 100644 --- a/integration/tests/helpers/panel.rs +++ b/integration/tests/helpers/panel.rs @@ -37,7 +37,7 @@ impl Panel { pub fn output( args: impl Into + Display, ) -> PanelResult { - eprintln!("EXEC >>> {PANEL_BINARY} {}", &args); + eprintln!(">>> {PANEL_BINARY} {}", &args); let splitted = shlex::split(args.into().as_ref()).unwrap(); let result = Self::output_argv( splitted diff --git a/lib/u_api_proc_macro/Cargo.toml b/lib/u_api_proc_macro/Cargo.toml deleted file mode 100644 index c0e8456..0000000 --- a/lib/u_api_proc_macro/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "u_api_proc_macro" -version = "0.1.0" -authors = ["plazmoid "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -proc-macro = true - -[dependencies] -syn = { version = "1.0", features = ["full", "extra-traits"] } -quote = "1.0" -strum = { version = "0.20", features = ["derive"] } -proc-macro2 = "1.0" \ No newline at end of file diff --git a/lib/u_api_proc_macro/src/lib.rs b/lib/u_api_proc_macro/src/lib.rs deleted file mode 100644 index a6d0e89..0000000 --- a/lib/u_api_proc_macro/src/lib.rs +++ /dev/null @@ -1,181 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Ident, TokenStream as TokenStream2}; -use quote::quote; -use std::{collections::HashMap, str::FromStr}; -use strum::EnumString; -use syn::{ - parse_macro_input, punctuated::Punctuated, AttributeArgs, FnArg, ItemFn, Lit, NestedMeta, - ReturnType, Signature, Token, Type, -}; - -#[derive(EnumString, Debug)] -enum ReqMethod { - GET, - POST, -} - -#[derive(Debug)] -struct Endpoint { - method: ReqMethod, -} - -#[derive(Debug)] -struct FnArgs { - url_param: Option, - payload: Option, -} - -#[proc_macro_attribute] -pub fn api_route(args: TokenStream, item: TokenStream) -> TokenStream { - let args: AttributeArgs = parse_macro_input!(args); - let input: ItemFn = parse_macro_input!(item); - let Signature { - ident, - inputs, - generics, - output, - .. - } = input.sig; - let (impl_generics, _, _) = generics.split_for_impl(); - let FnArgs { url_param, payload } = parse_fn_args(inputs); - let Endpoint { method } = parse_attr_args(args); - let url_path = build_url_path(&ident, &url_param); - let return_ty = match output { - ReturnType::Type(_, ty) => quote!(#ty), - ReturnType::Default => quote!(()), - }; - let request = match method { - ReqMethod::GET => build_get(url_path), - ReqMethod::POST => build_post(url_path, &payload), - }; - let url_param = match url_param { - Some(p) => quote!(, param: #p), - None => TokenStream2::new(), - }; - let payload = match payload { - Some(p) => quote!(, payload: #p), - None => TokenStream2::new(), - }; - let q = quote! { - pub async fn #ident #impl_generics( - &self #url_param #payload - ) -> UResult<#return_ty> { - let request = { - #request - }; - let response = request.send().await?; - let content_len = response.content_length(); - let is_success = match response.error_for_status_ref() { - Ok(_) => Ok(()), - Err(e) => Err(UError::from(e)) - }; - let resp = response.text().await?; - let result = match is_success { - Ok(_) => { - serde_json::from_str::>(&resp) - .map(|msg| msg.into_inner()) - .or_else(|e| { - match content_len { - Some(0) => Ok(Default::default()), - _ => Err(UError::NetError(e.to_string(), resp.clone())) - } - }) - }, - Err(UError::NetError(err_src, _)) => Err( - UError::NetError( - err_src, - resp - ) - ), - _ => unreachable!() - }; - Ok(result?) - } - }; - q.into() -} - -fn parse_fn_args(raw: Punctuated) -> FnArgs { - let mut arg: HashMap = raw - .into_iter() - .filter_map(|arg| { - if let FnArg::Typed(argt) = arg { - let mut arg_name = String::new(); - // did you think I won't overplay you? won't destroy? - |arg_ident| -> TokenStream { - let q: TokenStream = quote!(#arg_ident).into(); - arg_name = parse_macro_input!(q as Ident).to_string(); - TokenStream::new() - }(argt.pat); - if &arg_name != "url_param" && &arg_name != "payload" { - panic!("Wrong arg name: {}", &arg_name) - } - let arg_type = *argt.ty; - Some((arg_name, arg_type)) - } else { - None - } - }) - .collect(); - FnArgs { - url_param: arg.remove("url_param"), - payload: arg.remove("payload"), - } -} - -fn build_get(url: TokenStream2) -> TokenStream2 { - quote! { - let request = self.build_get(#url); - request - } -} - -fn build_post(url: TokenStream2, payload: &Option) -> TokenStream2 { - let pld = match payload { - Some(_) => quote! { - .json(&payload.as_message()) - }, - None => TokenStream2::new(), - }; - quote! { - let request = self.build_post(#url); - request #pld - } -} - -fn build_url_path(path: &Ident, url_param: &Option) -> TokenStream2 { - let url_param = match url_param { - Some(_) => quote! { - + &opt_to_string(param) - }, - None => TokenStream2::new(), - }; - quote! { - &format!( - "{}/{}", - stringify!(#path), - String::new() #url_param - ) - } -} - -fn parse_attr_args(args: AttributeArgs) -> Endpoint { - let mut args = args.into_iter(); - let method = match args.next() { - Some(method) => match method { - NestedMeta::Lit(l) => { - if let Lit::Str(s) = l { - match ReqMethod::from_str(&s.value()) { - Ok(v) => v, - Err(_) => panic!("Unknown method"), - } - } else { - panic!("Method must be a str") - } - } - _ => panic!("Method must be on the first place"), - }, - None => panic!("Method required"), - }; - Endpoint { method } -} diff --git a/lib/u_api_proc_macro/tests/tests.rs b/lib/u_api_proc_macro/tests/tests.rs deleted file mode 100644 index 7c4b404..0000000 --- a/lib/u_api_proc_macro/tests/tests.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* -use std::fmt::Display; -use u_api_proc_macro::api_route; - -type UResult = Result; - -struct ClientHandler; -struct Paths; - -#[test] -fn test1() { - #[api_route("GET", Uuid)] - fn list(url_param: T) {} -} -*/ diff --git a/lib/u_lib/Cargo.toml b/lib/u_lib/Cargo.toml index 972645e..88a2cc9 100644 --- a/lib/u_lib/Cargo.toml +++ b/lib/u_lib/Cargo.toml @@ -2,7 +2,7 @@ name = "u_lib" version = "0.1.0" authors = ["plazmoid "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,17 +16,20 @@ lazy_static = "1.4.0" futures = "0.3.5" thiserror = "*" log = "*" -env_logger = "0.8.3" diesel-derive-enum = { version = "1", features = ["postgres"] } chrono = "0.4.19" strum = { version = "0.20", features = ["derive"] } once_cell = "1.7.2" shlex = "1.0.0" -u_api_proc_macro = { version = "*", path = "../u_api_proc_macro" } crossbeam = "0.8.1" diesel = { version = "1.4.5", features = ["postgres", "uuid"] } envy = "0.4.2" serde_json = "1.0.81" +tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } +tracing-appender = "0.2.2" + +[features] +panel = [] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] reqwest = { version = "0.11", features = ["json", "native-tls"] } diff --git a/lib/u_lib/src/api.rs b/lib/u_lib/src/api.rs index 35af4d3..7bc0e6d 100644 --- a/lib/u_lib/src/api.rs +++ b/lib/u_lib/src/api.rs @@ -1,98 +1,134 @@ +use std::collections::HashMap; + use crate::{ config::MASTER_PORT, - messaging::{self, AsMsg, BaseMessage}, - models, - utils::{opt_to_string, VecDisplay}, + messaging::{self, AsMsg, BaseMessage, Empty}, + models::{self, Agent}, + utils::opt_to_string, UError, UResult, }; -use reqwest::{Certificate, Client, Identity, RequestBuilder, Url}; -use u_api_proc_macro::api_route; +use reqwest::{header::HeaderMap, Certificate, Client, Identity, Url}; +use serde::de::DeserializeOwned; use uuid::Uuid; const AGENT_IDENTITY: &[u8] = include_bytes!("../../../certs/alice.p12"); const ROOT_CA_CERT: &[u8] = include_bytes!("../../../certs/ca.crt"); +#[derive(Clone)] pub struct ClientHandler { base_url: Url, client: Client, - password: Option, } impl ClientHandler { - pub fn new(server: &str) -> Self { + pub fn new(server: &str, password: Option) -> Self { let identity = Identity::from_pkcs12_der(AGENT_IDENTITY, "").unwrap(); - let client = Client::builder() - .identity(identity) + let mut client = Client::builder().identity(identity); + if let Some(pwd) = password { + client = client.default_headers( + HeaderMap::try_from(&HashMap::from([( + "Authorization".to_string(), + format!("Bearer {pwd}"), + )])) + .unwrap(), + ) + } + let client = client .add_root_certificate(Certificate::from_pem(ROOT_CA_CERT).unwrap()) .build() .unwrap(); Self { client, base_url: Url::parse(&format!("https://{}:{}", server, MASTER_PORT)).unwrap(), - password: None, } } - pub fn password(mut self, password: String) -> ClientHandler { - self.password = Some(password); - self - } + async fn _req( + &self, + url: impl AsRef, + payload: P, + ) -> UResult { + let request = self + .client + .post(self.base_url.join(url.as_ref()).unwrap()) + .json(&payload.as_message()); - fn set_pwd(&self, rb: RequestBuilder) -> RequestBuilder { - match &self.password { - Some(p) => rb.bearer_auth(p), - None => rb, + let response = request.send().await?; + let is_success = match response.error_for_status_ref() { + Ok(_) => Ok(()), + Err(e) => Err(UError::from(e)), + }; + let resp = response.text().await?; + match is_success { + Ok(_) => serde_json::from_str::>(&resp) + .map(|msg| msg.into_inner()) + .or_else(|e| Err(UError::NetError(e.to_string(), resp.clone()))), + Err(UError::NetError(err, _)) => Err(UError::NetError(err, resp)), + _ => unreachable!(), } } - fn build_get(&self, url: &str) -> RequestBuilder { - let rb = self.client.get(self.base_url.join(url).unwrap()); - self.set_pwd(rb) + // get jobs for client + pub async fn get_personal_jobs( + &self, + url_param: Option, + ) -> UResult> { + self._req( + format!("get_personal_jobs/{}", opt_to_string(url_param)), + Empty, + ) + .await } - fn build_post(&self, url: &str) -> RequestBuilder { - let rb = self.client.post(self.base_url.join(url).unwrap()); - self.set_pwd(rb) - } - // - // get jobs for client - #[api_route("GET")] - async fn get_personal_jobs(&self, url_param: Option) -> VecDisplay {} - // // send something to server - #[api_route("POST")] - async fn report(&self, payload: &[messaging::Reportable]) -> messaging::Empty {} - // + pub async fn report(&self, payload: &[messaging::Reportable]) -> UResult { + self._req("report", payload).await + } + // download file - #[api_route("GET")] - async fn dl(&self, url_param: Option) -> Vec {} - // - // request download - #[api_route("POST")] - async fn dlr(&self, url_param: Option) -> messaging::DownloadInfo {} - - //##########// Admin area //##########// - /// client listing - #[api_route("GET")] - async fn get_agents(&self, url_param: Option) -> VecDisplay {} - // - // get all available jobs - #[api_route("GET")] - async fn get_jobs(&self, url_param: Option) -> VecDisplay {} - // - // create and upload job - #[api_route("POST")] - async fn upload_jobs(&self, payload: &[models::JobMeta]) -> messaging::Empty {} - // - // delete something - #[api_route("GET")] - async fn del(&self, url_param: Option) -> i32 {} - // - // set jobs for any client - #[api_route("POST")] - async fn set_jobs(&self, url_param: Option, payload: &[String]) -> VecDisplay {} - // - // get jobs for any client - #[api_route("GET")] - async fn get_agent_jobs(&self, url_param: Option) -> VecDisplay {} + pub async fn dl(&self, file: String) -> UResult> { + self._req(format!("dl/{file}"), Empty).await + } +} + +//##########// Admin area //##########// +#[cfg(feature = "panel")] +impl ClientHandler { + /// agent listing + pub async fn get_agents(&self, agent: Option) -> UResult> { + self._req(format!("get_agents/{}", opt_to_string(agent)), Empty) + .await + } + + /// update something + pub async fn update_item(&self, item: impl AsMsg) -> UResult { + self._req("update_item", item).await + } + + /// get all available jobs + pub async fn get_jobs(&self, job: Option) -> UResult> { + self._req(format!("get_jobs/{}", opt_to_string(job)), Empty) + .await + } + + /// create and upload job + pub async fn upload_jobs(&self, payload: &[models::JobMeta]) -> UResult { + self._req("upload_jobs", payload).await + } + + /// delete something + pub async fn del(&self, item: Uuid) -> UResult { + self._req(format!("del/{item}"), Empty).await + } + + /// set jobs for any agent + pub async fn set_jobs(&self, agent: Uuid, job_idents: &[String]) -> UResult> { + self._req(format!("set_jobs/{agent}"), job_idents).await + } + + /// get jobs for any agent + pub async fn get_agent_jobs(&self, agent: Option) -> UResult> { + self._req(format!("set_jobs/{}", opt_to_string(agent)), Empty) + .await + } } diff --git a/lib/u_lib/src/builder.rs b/lib/u_lib/src/builder.rs index c9efcb0..1c1f6ba 100644 --- a/lib/u_lib/src/builder.rs +++ b/lib/u_lib/src/builder.rs @@ -2,7 +2,7 @@ use crate::{ cache::JobCache, executor::{DynFut, Waiter}, messaging::Reportable, - models::{Agent, AssignedJob, JobMeta, JobType}, + models::{Agent, AssignedJob, JobMeta, JobType, RawJobMeta}, utils::{CombinedResult, OneOrVec}, UError, UResult, }; @@ -102,7 +102,7 @@ impl NamedJobBuilder { .into_vec() .into_iter() .filter_map( - |(alias, cmd)| match JobMeta::builder().with_shell(cmd).build() { + |(alias, cmd)| match RawJobMeta::builder().with_shell(cmd).build() { Ok(meta) => Some((alias, meta)), Err(e) => { result.err(e); @@ -164,7 +164,7 @@ mod tests { #[tokio::test] async fn test_is_really_async() { const SLEEP_SECS: u64 = 1; - let job = JobMeta::from_shell(format!("sleep {}", SLEEP_SECS)).unwrap(); + let job = RawJobMeta::from_shell(format!("sleep {}", SLEEP_SECS)).unwrap(); let sleep_jobs: Vec = (0..50).map(|_| job.clone()).collect(); let now = SystemTime::now(); JobBuilder::from_meta(sleep_jobs).unwrap_one().wait().await; @@ -201,7 +201,7 @@ mod tests { #[case] payload: Option<&[u8]>, #[case] expected_result: &str, ) -> TestResult { - let mut job = JobMeta::builder().with_shell(cmd); + let mut job = RawJobMeta::builder().with_shell(cmd); if let Some(p) = payload { job = job.with_payload(p); } @@ -217,12 +217,12 @@ mod tests { async fn test_complex_load() -> TestResult { const SLEEP_SECS: u64 = 1; let now = SystemTime::now(); - let longest_job = JobMeta::from_shell(format!("sleep {}", SLEEP_SECS)).unwrap(); + let longest_job = RawJobMeta::from_shell(format!("sleep {}", SLEEP_SECS)).unwrap(); let longest_job = JobBuilder::from_meta(longest_job) .unwrap_one() .spawn() .await; - let ls = JobBuilder::from_meta(JobMeta::from_shell("ls")?) + let ls = JobBuilder::from_meta(RawJobMeta::from_shell("ls")?) .unwrap_one() .wait_one() .await; @@ -231,7 +231,7 @@ mod tests { let folders = ls.to_string_result(); let subfolders_jobs: Vec = folders .lines() - .map(|f| JobMeta::from_shell(format!("ls {}", f)).unwrap()) + .map(|f| RawJobMeta::from_shell(format!("ls {}", f)).unwrap()) .collect(); let ls_subfolders = JobBuilder::from_meta(subfolders_jobs) .unwrap_one() @@ -265,7 +265,7 @@ mod tests { */ #[tokio::test] async fn test_failing_shell_job() -> TestResult { - let job = JobMeta::from_shell("lol_kek_puk")?; + let job = RawJobMeta::from_shell("lol_kek_puk")?; let job_result = JobBuilder::from_meta(job).unwrap_one().wait_one().await; let job_result = unwrap_enum!(job_result, Reportable::Assigned); let output = job_result.to_string_result(); @@ -283,7 +283,7 @@ mod tests { #[case] payload: Option<&[u8]>, #[case] err_str: &str, ) -> TestResult { - let mut job = JobMeta::builder().with_shell(cmd); + let mut job = RawJobMeta::builder().with_shell(cmd); if let Some(p) = payload { job = job.with_payload(p); } @@ -296,10 +296,10 @@ mod tests { #[tokio::test] async fn test_different_job_types() -> TestResult { let mut jobs = NamedJobBuilder::from_meta(vec![ - ("sleeper", JobMeta::from_shell("sleep 3")?), + ("sleeper", RawJobMeta::from_shell("sleep 3")?), ( "gatherer", - JobMeta::builder().with_type(JobType::Manage).build()?, + RawJobMeta::builder().with_type(JobType::Manage).build()?, ), ]) .wait() diff --git a/lib/u_lib/src/config.rs b/lib/u_lib/src/config.rs index e9d649a..50d3408 100644 --- a/lib/u_lib/src/config.rs +++ b/lib/u_lib/src/config.rs @@ -4,5 +4,9 @@ use uuid::Uuid; pub const MASTER_PORT: u16 = 63714; lazy_static! { - pub static ref UID: Uuid = Uuid::new_v4(); + static ref UID: Uuid = Uuid::new_v4(); +} + +pub fn get_self_uid() -> Uuid { + *UID } diff --git a/lib/u_lib/src/errors/variants.rs b/lib/u_lib/src/errors/variants.rs index 829d7ab..2a9a48f 100644 --- a/lib/u_lib/src/errors/variants.rs +++ b/lib/u_lib/src/errors/variants.rs @@ -32,7 +32,7 @@ pub enum UError { #[error("Job {0} doesn't exist")] NoJob(Uuid), - #[error("Error while processing {0}: {1}")] + #[error("FS error while processing {0}: {1}")] FSError(String, String), #[error("Wrong auth token")] @@ -43,6 +43,9 @@ pub enum UError { #[error("Panel error: {0}")] PanelError(String), + + #[error("Deserialize from json error: {0}")] + DeserializeError(String), } #[cfg(not(target_arch = "wasm32"))] @@ -51,3 +54,9 @@ impl From for UError { UError::NetError(e.to_string(), String::new()) } } + +impl From for UError { + fn from(e: serde_json::Error) -> Self { + UError::DeserializeError(e.to_string()) + } +} diff --git a/lib/u_lib/src/lib.rs b/lib/u_lib/src/lib.rs index 2b6fa07..41383a4 100644 --- a/lib/u_lib/src/lib.rs +++ b/lib/u_lib/src/lib.rs @@ -9,6 +9,7 @@ pub mod exports { pub mod datatypes; pub mod errors; pub mod executor; + pub mod logging; pub mod messaging; pub mod models; pub mod utils; @@ -24,7 +25,6 @@ pub mod exports { pub mod utils; } -pub use config::UID; pub use errors::{UError, UResult}; pub use exports::*; @@ -38,7 +38,6 @@ extern crate diesel; #[macro_use] extern crate log; -extern crate env_logger; #[cfg(test)] #[macro_use] diff --git a/lib/u_lib/src/logging.rs b/lib/u_lib/src/logging.rs new file mode 100644 index 0000000..cbd7dac --- /dev/null +++ b/lib/u_lib/src/logging.rs @@ -0,0 +1,16 @@ +use std::env; +use std::path::Path; + +use tracing_appender::rolling; +use tracing_subscriber::EnvFilter; + +pub fn init_logger(logfile: impl AsRef + Send + Sync + 'static) { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "info") + } + + tracing_subscriber::fmt::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_writer(move || rolling::never(".", logfile.as_ref().with_extension("log"))) + .init(); +} diff --git a/lib/u_lib/src/messaging/base.rs b/lib/u_lib/src/messaging/base.rs index 3cef82d..06b73a6 100644 --- a/lib/u_lib/src/messaging/base.rs +++ b/lib/u_lib/src/messaging/base.rs @@ -1,5 +1,5 @@ +use crate::config::get_self_uid; use crate::utils::VecDisplay; -use crate::UID; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::Display; @@ -43,7 +43,10 @@ impl<'cow, I: AsMsg> BaseMessage<'cow, I> { C: Into>, { let Moo(inner) = inner.into(); - Self { id: *UID, inner } + Self { + id: get_self_uid(), + inner, + } } pub fn into_inner(self) -> I { diff --git a/lib/u_lib/src/models/agent.rs b/lib/u_lib/src/models/agent.rs index 7c238a8..6843e0e 100644 --- a/lib/u_lib/src/models/agent.rs +++ b/lib/u_lib/src/models/agent.rs @@ -6,7 +6,10 @@ use strum::Display; #[cfg(not(target_arch = "wasm32"))] use crate::builder::NamedJobBuilder; -use crate::{messaging::Reportable, models::schema::*, unwrap_enum, utils::systime_to_string, UID}; +use crate::{ + config::get_self_uid, messaging::Reportable, models::schema::*, unwrap_enum, + utils::systime_to_string, +}; use uuid::Uuid; @@ -107,7 +110,7 @@ impl Default for Agent { fn default() -> Self { Self { alias: None, - id: *UID, + id: get_self_uid(), hostname: String::new(), is_root: false, is_root_allowed: false, diff --git a/lib/u_lib/src/models/jobs/assigned.rs b/lib/u_lib/src/models/jobs/assigned.rs index 052f02b..d2465b6 100644 --- a/lib/u_lib/src/models/jobs/assigned.rs +++ b/lib/u_lib/src/models/jobs/assigned.rs @@ -2,11 +2,11 @@ use super::JobState; #[cfg(not(target_arch = "wasm32"))] use crate::{cache::JobCache, utils::TempFile}; use crate::{ + config::get_self_uid, errors::UError, messaging::Reportable, models::schema::*, utils::{systime_to_string, ProcOutput}, - UID, }; use diesel::{Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; @@ -119,7 +119,7 @@ impl AssignedJob { pub fn new(job_id: Uuid, other: Option<&Self>) -> Self { Self { - agent_id: *UID, + agent_id: get_self_uid(), job_id, ..other.unwrap_or(&Default::default()).clone() } diff --git a/lib/u_lib/src/models/jobs/meta.rs b/lib/u_lib/src/models/jobs/meta.rs index 939d256..6074c31 100644 --- a/lib/u_lib/src/models/jobs/meta.rs +++ b/lib/u_lib/src/models/jobs/meta.rs @@ -2,11 +2,14 @@ use super::JobType; use crate::{models::schema::*, utils::Stripped, UError, UResult}; use diesel::{Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::path::PathBuf; use std::str::from_utf8; +use std::{fmt, fs}; use uuid::Uuid; -#[derive(Serialize, Deserialize, Clone, Debug, Queryable, Identifiable, Insertable)] +#[derive( + Serialize, Deserialize, Clone, Debug, Queryable, Identifiable, Insertable, AsChangeset, +)] #[table_name = "jobs"] pub struct JobMeta { pub alias: Option, @@ -20,12 +23,28 @@ pub struct JobMeta { pub payload: Option>, } -impl JobMeta { +#[derive(Deserialize)] +pub struct RawJobMeta { + pub alias: Option, + pub argv: String, + pub id: Uuid, + pub exec_type: JobType, + //pub schedule: JobSchedule, + pub platform: String, + pub payload: Option>, + pub payload_path: Option, +} + +impl RawJobMeta { pub fn builder() -> JobMetaBuilder { JobMetaBuilder::default() } - pub fn from_shell(cmd: impl Into) -> UResult { + pub fn into_builder(self) -> JobMetaBuilder { + JobMetaBuilder { inner: self } + } + + pub fn from_shell(cmd: impl Into) -> UResult { Self::builder().with_shell(cmd).build() } } @@ -60,29 +79,35 @@ impl Default for JobMeta { alias: None, argv: String::new(), exec_type: JobType::Shell, - #[cfg(not(target_arch = "wasm32"))] platform: guess_host_triple::guess_host_triple() .unwrap_or("unknown") .to_string(), - #[cfg(target_arch = "wasm32")] - platform: "unknown".to_string(), payload: None, } } } -pub struct JobMetaBuilder { - inner: JobMeta, -} - -impl Default for JobMetaBuilder { +impl Default for RawJobMeta { fn default() -> Self { Self { - inner: JobMeta::default(), + id: Uuid::new_v4(), + alias: None, + argv: String::new(), + exec_type: JobType::Shell, + platform: guess_host_triple::guess_host_triple() + .unwrap_or("unknown") + .to_string(), + payload: None, + payload_path: None, } } } +#[derive(Default)] +pub struct JobMetaBuilder { + inner: RawJobMeta, +} + impl JobMetaBuilder { pub fn with_shell(mut self, shell_cmd: impl Into) -> Self { self.inner.argv = shell_cmd.into(); @@ -94,6 +119,11 @@ impl JobMetaBuilder { self } + pub fn with_payload_src(mut self, path: impl Into) -> Self { + self.inner.payload_path = Some(path.into()); + self + } + pub fn with_alias(mut self, alias: impl Into) -> Self { self.inner.alias = Some(alias.into()); self @@ -117,6 +147,12 @@ impl JobMetaBuilder { if argv_parts.get(0).ok_or(empty_err.clone())?.is_empty() { return Err(empty_err.into()); } + if let Some(path) = &inner.payload_path { + let data = fs::read(path.clone()).map_err(|e| { + UError::FSError(path.to_string_lossy().to_string(), e.to_string()) + })?; + inner.payload = Some(data) + } match inner.payload.as_ref() { Some(_) => { if !inner.argv.contains("{}") { @@ -136,20 +172,23 @@ impl JobMetaBuilder { } } }; - Ok(inner) + Ok(inner.into()) } - JobType::Manage => Ok(inner), + JobType::Manage => Ok(inner.into()), _ => todo!(), } } - /* - pub fn from_file(path: PathBuf) -> UResult { - let data = fs::read(path) - .map_err(|e| UError::FilesystemError( - path.to_string_lossy().to_string(), - e.to_string() - ))?; - let filename = path.file_name().unwrap().to_str().unwrap(); - - }*/ +} + +impl From for JobMeta { + fn from(rjm: RawJobMeta) -> Self { + JobMeta { + alias: rjm.alias, + argv: rjm.argv, + id: rjm.id, + exec_type: rjm.exec_type, + platform: rjm.platform, + payload: rjm.payload, + } + } } diff --git a/lib/u_lib/src/models/jobs/misc.rs b/lib/u_lib/src/models/jobs/misc.rs index ebcd38d..2eb4ce4 100644 --- a/lib/u_lib/src/models/jobs/misc.rs +++ b/lib/u_lib/src/models/jobs/misc.rs @@ -27,11 +27,12 @@ pub enum JobState { Finished, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, DbEnum, Display)] +#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, DbEnum, Display)] #[PgType = "JobType"] #[DieselType = "Jobtype"] pub enum JobType { Manage, + #[default] Shell, Python, } diff --git a/lib/u_lib/src/utils/combined_result.rs b/lib/u_lib/src/utils/combined_result.rs index 05b92d1..b9a7125 100644 --- a/lib/u_lib/src/utils/combined_result.rs +++ b/lib/u_lib/src/utils/combined_result.rs @@ -1,12 +1,14 @@ +use std::fmt::Debug; + use crate::utils::OneOrVec; use crate::UError; -pub struct CombinedResult { +pub struct CombinedResult { ok: Vec, err: Vec, } -impl CombinedResult { +impl CombinedResult { pub fn new() -> Self { Self { ok: vec![], @@ -25,7 +27,7 @@ impl CombinedResult { pub fn unwrap(self) -> Vec { let err_len = self.err.len(); if err_len > 0 { - panic!("CombinedResult has {} errors", err_len); + panic!("CombinedResult has errors: {:?}", self.err); } self.ok } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 0804965..bb019c0 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -8,7 +8,7 @@ REMOTE_PATH=$SERVER:$REMOTE_DIR RSYNC="rsync -arzh --progress" ssh $SERVER mkdir -p $REMOTE_DIR/{release,deploy} -$RSYNC $ROOTDIR/release/u_server $REMOTE_PATH/release/u_server +$RSYNC $ROOTDIR/target/x86_64-unknown-linux-musl/release/u_server $REMOTE_PATH/release/u_server $RSYNC $ROOTDIR/certs/server.{crt,key} $REMOTE_PATH/certs $RSYNC $ROOTDIR/certs/ca.crt $REMOTE_PATH/certs $RSYNC $ROOTDIR/migrations/ $REMOTE_PATH/migrations diff --git a/scripts/gen_certs.sh b/scripts/gen_certs.sh index 3be58ea..af36f2c 100755 --- a/scripts/gen_certs.sh +++ b/scripts/gen_certs.sh @@ -13,6 +13,7 @@ subjectAltName = @alt_names [alt_names] DNS.1 = ortem.xyz DNS.2 = u_server +DNS.3 = localhost EOF openssl req -x509 -newkey rsa:4096 -keyout $DIR/ca.key -out $DIR/ca.crt -nodes -days 365 -subj "/CN=root"