This commit is contained in:
Smaug123
2023-12-22 12:38:31 +00:00
parent 9a79297e36
commit 7bbb0cbdef
2 changed files with 141 additions and 52 deletions

View File

@@ -11,6 +11,7 @@ type PipelineModifier =
// TODO: does this stack the right way? Does last win or first win?
WorkingDir : DirectoryInfo Comp option
AgentModifier : string Comp option
Labels : string list
}
override this.ToString () =
let agent =
@@ -19,6 +20,11 @@ type PipelineModifier =
| Some c ->
"New agent with image: " + Comp.force c
let labels =
this.Labels
|> String.concat ", "
|> function | "" -> "" | s -> $"; Labels: %s{s}"
let envVars =
if this.EnvVars.IsEmpty then
""
@@ -32,48 +38,59 @@ type PipelineModifier =
| None -> ""
| Some dir -> "; Working dir: " + (Comp.force dir).FullName
agent + envVars + workingDir
agent + labels + envVars + workingDir
type Pipeline =
private
| Empty
| ShellScript of script : string * andThen : (Pipeline)
| ShellScript of script : string * andThen : Pipeline
| ShellScriptBind of script : string * consumeStdout : (Stdout -> Pipeline)
| Sequence of first : (Pipeline * PipelineModifier) * second : (Pipeline)
[<RequireQualifiedAccess>]
module Pipeline =
let rec private toStringInner (bindCount : int) (indentCount : int) (this : Pipeline) : string =
let indent = String.replicate indentCount " "
match this with
| Pipeline.Empty -> $"%s{indent}<nothing>"
| Pipeline.ShellScript(script, (andThen)) ->
$"%s{indent}Run: %s{script}\n%s{indent}Then\n%s{toStringInner bindCount (indentCount + 2) andThen}"
| Pipeline.ShellScriptBind(script, consumeStdout) ->
let outputDummy = $"<stdout-%i{bindCount}>"
let dummyPipeline = consumeStdout (Stdout (Comp.make outputDummy))
$"%s{indent}Run: %s{script}\n%s{indent}With its output labelled %s{outputDummy}:\n%s{toStringInner (bindCount + 1) (indentCount + 2) dummyPipeline}"
| Pipeline.Sequence((first, modifier), second) ->
let firstMod = $"%s{indent} %O{modifier}\n"
let first = $"%s{toStringInner bindCount (indentCount + 2) first}"
let second = $"%s{toStringInner bindCount (indentCount + 2) second}"
$"%s{firstMod}%s{first}\n%s{indent}----\n%s{second}"
let toString (p : Pipeline) = toStringInner 0 0 p
| Sequence of previous : (Pipeline * PipelineModifier) list * final : Pipeline
[<RequireQualifiedAccess>]
module private PipelineModifier =
let empty (agent : string Comp option) : PipelineModifier =
let empty (newAgent : string Comp option) : PipelineModifier =
{
EnvVars = []
WorkingDir = None
AgentModifier = agent
AgentModifier = newAgent
Labels = []
}
let withEnv (key : string) (value : string Comp) (existing : PipelineModifier) =
{ existing with
EnvVars = (key, value) :: existing.EnvVars
}
let replaceWorkingDir (newDir : DirectoryInfo Comp) (existing : PipelineModifier) =
{ existing with WorkingDir = Some newDir }
let addLabel (label : string) (existing : PipelineModifier) =
{ existing with Labels = label :: existing.Labels }
[<Sealed ; Class>]
type PipelineBuilder (agent : string Comp option) =
let mutable modifier : PipelineModifier =
PipelineModifier.empty agent
[<CustomOperation "labelled">]
member _.WithLabel
(mods : PipelineModifier, label : string)
: PipelineModifier
=
{ mods with
Labels = label :: mods.Labels
}
[<CustomOperation "labelled">]
member _.WithLabel
(() : unit, label : string)
: PipelineModifier
=
{ PipelineModifier.empty agent with
Labels = [label]
}
[<CustomOperation "withEnv">]
member _.WithEnv
(mods : PipelineModifier, (key : string, value : string Comp))
@@ -127,7 +144,7 @@ type PipelineBuilder (agent : string Comp option) =
Pipeline.ShellScript (toRun, subsequent)
/// Bind in another pipeline, perhaps on a different agent
member _.Bind (p : Pipeline * PipelineModifier, cont : unit -> Pipeline) : Pipeline =
Pipeline.Sequence (p, cont ())
Pipeline.Sequence ([p], cont ())
member _.Yield (x : unit) : unit = ()
member _.For
(expr : PipelineModifier, cont : unit -> Pipeline)
@@ -140,33 +157,50 @@ type PipelineBuilder (agent : string Comp option) =
member _.Run x =
x, modifier
[<AutoOpen>]
module PipelineBuilder =
let pipeline (agent : string Comp) = PipelineBuilder (Some agent)
let pipelineSameAgent = PipelineBuilder None
[<RequireQualifiedAccess>]
module Pipeline =
let rec private toStringInner (bindCount : int) (indentCount : int) (this : Pipeline) : string =
let indent = String.replicate indentCount " "
match this with
| Pipeline.Empty -> $"%s{indent}<nothing>"
| Pipeline.ShellScript(script, andThen) ->
$"%s{indent}Run: %s{script}\n%s{indent}Then\n%s{toStringInner bindCount (indentCount + 2) andThen}"
| Pipeline.ShellScriptBind(script, consumeStdout) ->
let outputDummy = $"<stdout-%i{bindCount}>"
let dummyPipeline = consumeStdout (Stdout (Comp.make outputDummy))
$"%s{indent}Run: %s{script}\n%s{indent}With its output labelled %s{outputDummy}:\n%s{toStringInner (bindCount + 1) (indentCount + 2) dummyPipeline}"
| Pipeline.Sequence(previous, second) ->
let previous =
previous
|> List.map (fun (previous, modifier) ->
let firstMod = $"%s{indent} %O{modifier}\n"
let first = $"%s{toStringInner bindCount (indentCount + 2) previous}"
$"%s{firstMod}%s{first}"
)
|> String.concat $"\n%s{indent}-----\n"
let second = $"%s{toStringInner bindCount (indentCount + 2) second}"
$"%s{previous}%s{indent}----\n%s{second}"
let toString (p : Pipeline) = toStringInner 0 0 p
let empty = Pipeline.Empty
let prependShellScript script andThen =
Pipeline.ShellScript (script, andThen)
let concat (prevStages : (Pipeline * PipelineModifier) list) (final : Pipeline) =
Pipeline.Sequence (prevStages, final)
let bindShellScript script andThen =
Pipeline.ShellScriptBind (script, andThen)
let toStepDag (p : Pipeline, modifier : PipelineModifier) : SealedStepDag<unit, unit> =
match modifier.AgentModifier with
| None -> failwith "Pipeline must run on an agent, but no agent was specified"
| Some _ ->
failwith $"TODO\n%O{modifier}\n%s{Pipeline.toString p}"
failwith $"TODO\n%O{modifier}\n%s{toString p}"
let foo () : SealedStepDag<unit, unit> =
pipeline (Comp.make "some-image") {
workingDir (Comp.make (DirectoryInfo "code root here"))
withEnv ("foo", Comp.make "bar")
withEnv ("hi", Comp.make "bye")
let! (Stdout stdout) = "sh script here"
do! pipelineSameAgent {
withEnv ("foo", stdout)
do! "git config --foo"
return ()
}
do! pipeline (Comp.make "another-image") {
let! (Stdout stdout) = "maybe run a formatter"
return ()
}
do! "a shell script"
let! (Stdout stdout) = "another shell script"
return ()
}
|> toStepDag
[<AutoOpen>]
module PipelineBuilder =
let pipeline (agent : string Comp) = PipelineBuilder (Some agent)
let pipelineSameAgent = PipelineBuilder None

View File

@@ -1,8 +1,63 @@
namespace WoofWorkflows
open System.IO
module Program =
let foo () =
pipeline (Comp.make "some-image") {
workingDir (Comp.make (DirectoryInfo "code root here"))
labelled "label 1"
withEnv ("foo", Comp.make "bar")
withEnv ("hi", Comp.make "bye")
let! (Stdout stdout) = "sh script here"
do! pipelineSameAgent {
withEnv ("foo", stdout)
do! "git config --foo"
return ()
}
do! pipeline (Comp.make "another-image") {
labelled "label 2"
let! (Stdout stdout) = "maybe run a formatter"
return ()
}
do! "a shell script"
let! (Stdout stdout) = "another shell script"
return ()
}
let foo2 () =
let pl =
Pipeline.bindShellScript "sh script here" (fun (Stdout stdout) ->
Pipeline.concat
[
let modifier = PipelineModifier.empty None |> PipelineModifier.withEnv "foo" stdout
let pl = Pipeline.empty |> Pipeline.prependShellScript "git config --foo"
yield pl, modifier
let modifier = PipelineModifier.empty (Some (Comp.make "another-image")) |> PipelineModifier.addLabel "label 2"
let pl = Pipeline.bindShellScript "maybe run a formatter" (fun _ -> Pipeline.empty)
yield pl, modifier
let modifier = PipelineModifier.empty None
let pl = Pipeline.prependShellScript "a shell script" Pipeline.empty
yield pl, modifier
]
(Pipeline.bindShellScript "another shell script" (fun (Stdout stdout) ->
Pipeline.empty
))
)
let modifier =
PipelineModifier.empty (Some (Comp.make "some-image"))
|> PipelineModifier.replaceWorkingDir (Comp.make (DirectoryInfo "code root here"))
|> PipelineModifier.addLabel "label 1"
|> PipelineModifier.withEnv "foo" (Comp.make "bar")
|> PipelineModifier.withEnv "hi" (Comp.make "bye")
pl, modifier
[<EntryPoint>]
let main argv =
PipelineBuilder.foo ()
foo2 ()
|> Pipeline.toStepDag
|> ignore
0