This commit is contained in:
bparodi@lezzo.org 2024-10-28 19:33:36 +01:00
parent e037b0b102
commit 474f0f1a04
7 changed files with 67 additions and 27 deletions

View file

@ -7,4 +7,4 @@ build:
run: run:
cd entrypoint && dotnet run -- --user ${USER} --password ${PASSWORD} -H ${HOST} cd entrypoint && dotnet run -- --user ${USER} --password ${PASSWORD} -H ${HOST}
test: test:
dotnet test cd tests && dotnet test

View file

@ -8,7 +8,6 @@ open Pentole
open Datatypes open Datatypes
open Pentole.String open Pentole.String
open Pentole.Path
open Pentole.Map open Pentole.Map
type User = string type User = string
@ -23,13 +22,13 @@ let private parse_expr (now: Instant) text =
| true -> Ok c | true -> Ok c
| false -> Error $"Can't parse cron expression: {text}" | false -> Error $"Can't parse cron expression: {text}"
let to_pattern text = let to_pattern (text: string) =
match Pentole.String.split " " text |> List.head with match text with
| Prefix "@after" job -> After job |> Ok | Prefix "@after" job -> After job |> Ok
| _ -> Error $"Can't parse as pattern: {text}" | _ -> Error $"Can't parse as pattern: {text}"
match to_cron text, to_pattern text with match to_cron text, to_pattern text with
| Error e, Error _ -> Error e | Error e, Error p -> Error $"Can't parse {text} neither as pattern or cron expression"
| _, Ok p -> Ok (Pattern p) | _, Ok p -> Ok (Pattern p)
| Ok cron_expr, _ -> | Ok cron_expr, _ ->
(now.ToDateTimeOffset(), local_tz_net) (now.ToDateTimeOffset(), local_tz_net)
@ -40,7 +39,7 @@ let private parse_expr (now: Instant) text =
else Instant.FromDateTimeOffset i.Value |> Cron |> Ok) else Instant.FromDateTimeOffset i.Value |> Cron |> Ok)
let private parse now (db: Database.Requirements) = let private parse now (db: Requirements) =
let when_ = parse_expr now db.``when`` let when_ = parse_expr now db.``when``
let env = let env =
db.environment db.environment
@ -52,7 +51,7 @@ let private parse now (db: Database.Requirements) =
else else
Ok (splitted.[0], splitted.[1])) Ok (splitted.[0], splitted.[1]))
let executable = Path.of_string db.executable |> Result.bind FileSystem.resolve let executable = Which.which db.executable
let workdir = Path.of_string db.workdir |> Result.bind FileSystem.resolve let workdir = Path.of_string db.workdir |> Result.bind FileSystem.resolve
let last_completed = let last_completed =
@ -76,8 +75,7 @@ let private parse now (db: Database.Requirements) =
type JobKey = { type JobKey = {
j: string; h: string j: string; h: string
} }
let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) = let sort_cron_jobs (now: Instant) (db_crons: Requirements seq) =
let build_deps (lst: CronJob list) = let build_deps (lst: CronJob list) =
let standalone, deps = let standalone, deps =
@ -90,7 +88,7 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
in index, deps in index, deps
in in
let rec build_job_table acc (index: Map<JobKey, CronJob>) = function let rec build_job_table acc (index: Map<JobKey, CronJob>) = function
| [] -> Ok acc | [] -> Map.values acc |> Ok
| {when_=Cron _}::_ -> invalidOp "The jobs should have been partitioned" | {when_=Cron _}::_ -> invalidOp "The jobs should have been partitioned"
| {when_=Pattern (After jb)} as x::xs -> | {when_=Pattern (After jb)} as x::xs ->
let father = {j=jb; h=x.hostname} let father = {j=jb; h=x.hostname}
@ -100,7 +98,7 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
Map.find father index |> Result.map List.singleton Map.find father index |> Result.map List.singleton
| Some p -> Ok p | Some p -> Ok p
if previous |> Result.isError then if previous |> Result.isError then
Error "Invalid job definition. No such job_name {jb} in host {x.hostname}" Error $"Invalid job definition. No such job_name {jb} in host {x.hostname}"
else else
let p = Result.get previous let p = Result.get previous
let acc' = Map.add father (x::p) acc let acc' = Map.add father (x::p) acc
@ -109,4 +107,4 @@ let sort_cron_jobs (now: Instant) (db_crons: Database.Requirements seq) =
db_crons db_crons
|> ResultList.collect (parse now) |> ResultList.collect (parse now)
|> Result.map build_deps |> Result.map build_deps
|> Result.map (fun (standalone, deps) -> build_job_table Map.empty standalone deps) |> Result.bind (fun (standalone, deps) -> build_job_table Map.empty standalone deps)

View file

@ -9,6 +9,8 @@ open Serilog
open Npgsql open Npgsql
open Dapper open Dapper
open Datatypes
let private connstring = let private connstring =
let c = Environment.Environment() let c = Environment.Environment()
$"Server={c.pg_host};Database={c.pg_dbname};" + $"Server={c.pg_host};Database={c.pg_dbname};" +
@ -58,19 +60,6 @@ let make (logger: ILogger) =
let wait_notification (ct: CancellationToken) (db: t) = let wait_notification (ct: CancellationToken) (db: t) =
db.connection.WaitAsync ct db.connection.WaitAsync ct
[<CLIMutable>]
type Requirements = {
job_name: string
``when``: string
executable: string
user: string
workdir: string
hostname: string
args: string array
environment: string
done_at: System.DateTime option
}
let gather_requirements (hostname: string) (ct: CancellationToken) (db: t) = let gather_requirements (hostname: string) (ct: CancellationToken) (db: t) =
let query = """select let query = """select
c.job_name, c."when", c.executable, c.user, c.workdir, c.args, h.hostname, c.job_name, c."when", c.executable, c.user, c.workdir, c.args, h.hostname,

View file

@ -27,3 +27,16 @@ type CronJob = {
hostname: string hostname: string
last_completed_at: Instant last_completed_at: Instant
} }
[<CLIMutable>]
type Requirements = {
job_name: string
``when``: string
executable: string
user: string
workdir: string
hostname: string
args: string array
environment: string
done_at: System.DateTime option
}

View file

@ -5,3 +5,6 @@ module Map =
match Map.tryFind key map with match Map.tryFind key map with
| Some v -> Ok v | Some v -> Ok v
| None -> Error $"Can't find key {key}" | None -> Error $"Can't find key {key}"
let values (map: Map<'k, 'v>): 'v seq =
Map.values map

View file

@ -11,6 +11,7 @@
<Compile Include="Map.fs" /> <Compile Include="Map.fs" />
<Compile Include="Result.fs" /> <Compile Include="Result.fs" />
<Compile Include="String.fs" /> <Compile Include="String.fs" />
<Compile Include="Which.fs" />
<Compile Include="Datatypes.fs" /> <Compile Include="Datatypes.fs" />
<Compile Include="Grains.fs" /> <Compile Include="Grains.fs" />
<Compile Include="DatabaseMigrations.fs" /> <Compile Include="DatabaseMigrations.fs" />
@ -38,6 +39,7 @@
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Sheller" Version="6.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -2,7 +2,42 @@ module tests
open NUnit.Framework open NUnit.Framework
open Pentole.TestsExtensions open Pentole.TestsExtensions
open Bidello.Datatypes
open Bidello
open Pentole.String
[<Test>] [<Test>]
let Test1 () = let string_prefix_active_pattern () =
Assert.Pass() match "@after job" with
| Prefix "@after" j -> Assert.Pass ()
| _ -> Assert.Pass ()
match "@after job " with
| Prefix "@after" j -> Assert.Pass ()
| _ -> Assert.Pass ()
let bj =
{ job_name = "j1"
hostname = "h1"
``when`` = "* * * * *"
executable = "echo"
workdir = "/"; user = "nobody"
args = [||]; environment = "";
done_at = None }
[<Test>]
let job_deps () =
let requirements = [
bj
{bj with job_name = "j2"}
{bj with hostname = "h2"}
{bj with job_name = "j1_after"; ``when``="@after j1"}
// TODO: another test with this at h2
]
let now = NodaTime.SystemClock.Instance.GetCurrentInstant ()
let cjs = Cron.sort_cron_jobs now requirements
Assert.ok_is_equal Seq.empty cjs