ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c

View 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