ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
157
services/project-delivery/k8s/workflows/controller/plan.fsx
Normal file
157
services/project-delivery/k8s/workflows/controller/plan.fsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#load "contract.fsx"
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Diagnostics
|
||||
open System.IO
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Serialization
|
||||
open Contract
|
||||
|
||||
[<CLIMutable>]
|
||||
type CacheObject =
|
||||
{ [<JsonPropertyName("key")>]
|
||||
Key: string
|
||||
[<JsonPropertyName("sha256")>]
|
||||
Sha256: string }
|
||||
|
||||
[<CLIMutable>]
|
||||
type Assignment =
|
||||
{ [<JsonPropertyName("origin")>]
|
||||
Origin: string
|
||||
[<JsonPropertyName("cache")>]
|
||||
Cache: CacheObject
|
||||
[<JsonPropertyName("exportKey")>]
|
||||
ExportKey: string
|
||||
[<JsonPropertyName("cacheVersion")>]
|
||||
CacheVersion: string
|
||||
[<JsonPropertyName("inputHashes")>]
|
||||
InputHashes: Dictionary<string, string> }
|
||||
|
||||
[<CLIMutable>]
|
||||
type InputPlan =
|
||||
{ [<JsonPropertyName("version")>]
|
||||
Version: int
|
||||
[<JsonPropertyName("project")>]
|
||||
Project: string
|
||||
[<JsonPropertyName("sourceRevision")>]
|
||||
SourceRevision: string
|
||||
[<JsonPropertyName("toolchain")>]
|
||||
Toolchain: Assignment
|
||||
[<JsonPropertyName("build")>]
|
||||
Build: Assignment }
|
||||
|
||||
let private planPath = "/state/cache-plan.json"
|
||||
let private sourceRoot = "/source/repository"
|
||||
|
||||
let private controllerRoot =
|
||||
Path.Combine(sourceRoot, "services/project-delivery/controller")
|
||||
|
||||
let private identity fields =
|
||||
fields |> String.concat "\n" |> (fun value -> hashText (value + "\n"))
|
||||
|
||||
let private inputHashes () =
|
||||
let hashes = Dictionary<string, string>()
|
||||
|
||||
[ "services/project-delivery/controller/go.mod"
|
||||
"services/project-delivery/controller/go.sum" ]
|
||||
|> List.iter (fun path ->
|
||||
let source = Path.Combine(sourceRoot, path)
|
||||
require (File.Exists source) $"missing cache input: {path}"
|
||||
hashes[path] <- sha256 source)
|
||||
|
||||
hashes
|
||||
|
||||
let private cacheVersion name =
|
||||
let files =
|
||||
match name with
|
||||
| "toolchain" ->
|
||||
[ "cache.fsx"
|
||||
"contract.fsx"
|
||||
"controller.apko.yaml"
|
||||
"plan.fsx"
|
||||
"toolchain.fsx" ]
|
||||
| "go-build" -> [ "build.fsx"; "cache.fsx"; "contract.fsx"; "plan.fsx"; "toolchain.fsx" ]
|
||||
| _ -> fail "invalid cache name"
|
||||
|
||||
[ "controller-delivery-cache-v1"
|
||||
name
|
||||
"apko=1.2.39-r0"
|
||||
"go-1.27=1.27.3-r0"
|
||||
"ca-certificates-bundle=20261020-r0" ]
|
||||
@ (files
|
||||
|> List.map (fun path ->
|
||||
let digest = sha256 (Path.Combine("/orchestration", path))
|
||||
$"{path}={digest}"))
|
||||
|> identity
|
||||
|
||||
let private assignment (name: string) (hashes: Dictionary<string, string>) =
|
||||
{ Origin = "primary"
|
||||
Cache = Unchecked.defaultof<CacheObject>
|
||||
ExportKey = $"projects/controller/controller-delivery/cache/{name}/_latest.json"
|
||||
CacheVersion = cacheVersion name
|
||||
InputHashes = Dictionary<string, string>(hashes) }
|
||||
|
||||
let create (revision: string) (select: string -> Assignment -> Assignment) =
|
||||
require (validRevision revision) "invalid source revision"
|
||||
require (Directory.Exists controllerRoot) "controller source is unavailable"
|
||||
let hashes = inputHashes ()
|
||||
|
||||
{ Version = 1
|
||||
Project = "controller"
|
||||
SourceRevision = revision
|
||||
Toolchain = select "toolchain" (assignment "toolchain" hashes)
|
||||
Build = select "go-build" (assignment "go-build" hashes) }
|
||||
|
||||
let write (plan: InputPlan) =
|
||||
directory (Path.GetDirectoryName planPath) |> ignore
|
||||
let temporary = planPath + ".incoming"
|
||||
File.WriteAllText(temporary, JsonSerializer.Serialize(plan, json))
|
||||
File.Move(temporary, planPath, true)
|
||||
|
||||
let private validateAssignment (name: string) (assignment: Assignment) =
|
||||
require (not <| isNull (box assignment)) $"missing {name} assignment"
|
||||
|
||||
require
|
||||
(assignment.ExportKey = $"projects/controller/controller-delivery/cache/{name}/_latest.json")
|
||||
$"invalid {name} cache key"
|
||||
|
||||
require (validSha256 assignment.CacheVersion) $"invalid {name} cache version"
|
||||
require (not <| isNull (box assignment.InputHashes)) $"missing {name} input hashes"
|
||||
|
||||
assignment.InputHashes
|
||||
|> Seq.iter (fun item ->
|
||||
require (not (String.IsNullOrWhiteSpace item.Key) && validSha256 item.Value) $"invalid {name} input hash")
|
||||
|
||||
match assignment.Origin with
|
||||
| "primary" -> require (isNull (box assignment.Cache)) $"unexpected {name} cache object"
|
||||
| "cache" ->
|
||||
require
|
||||
(not <| isNull (box assignment.Cache)
|
||||
&& assignment.Cache.Key.StartsWith(
|
||||
$"projects/controller/controller-delivery/cache/{name}/objects/",
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
&& assignment.Cache.Key.EndsWith(".tar.zst", StringComparison.Ordinal)
|
||||
&& validSha256 assignment.Cache.Sha256)
|
||||
$"invalid {name} cache object"
|
||||
| _ -> fail $"invalid {name} cache origin"
|
||||
|
||||
let read () =
|
||||
let elapsed = Stopwatch.StartNew()
|
||||
|
||||
while not (File.Exists planPath) && elapsed.Elapsed < TimeSpan.FromMinutes 15.0 do
|
||||
sleep 100
|
||||
|
||||
require (File.Exists planPath) "cache plan unavailable"
|
||||
let plan = File.ReadAllText(planPath) |> decode<InputPlan>
|
||||
|
||||
require
|
||||
(plan.Version = 1
|
||||
&& plan.Project = "controller"
|
||||
&& validRevision plan.SourceRevision)
|
||||
"invalid cache plan identity"
|
||||
|
||||
validateAssignment "toolchain" plan.Toolchain
|
||||
validateAssignment "go-build" plan.Build
|
||||
plan
|
||||
Loading…
Reference in a new issue