ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
199
services/project-delivery/k8s/workflows/controller/build.fsx
Normal file
199
services/project-delivery/k8s/workflows/controller/build.fsx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#load "plan.fsx"
|
||||
|
||||
open System
|
||||
open System.Formats.Tar
|
||||
open System.IO
|
||||
open System.IO.Compression
|
||||
open System.Security.Cryptography
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Nodes
|
||||
open Contract
|
||||
open Plan
|
||||
|
||||
let revision = Environment.GetEnvironmentVariable "SOURCE_REVISION"
|
||||
let plan = read ()
|
||||
let source = "/source/repository/services/project-delivery/controller"
|
||||
let output = "/output"
|
||||
let layout = Path.Combine(output, "layout")
|
||||
let baseImage = "/toolchain/image-base"
|
||||
let go = "/toolchain/usr/lib/go/bin/go"
|
||||
|
||||
require (validRevision revision && plan.SourceRevision = revision) "source revision mismatch"
|
||||
require (File.Exists go && File.Exists "/toolchain/.ready") "toolchain unavailable"
|
||||
require (Directory.Exists source) "controller source unavailable"
|
||||
require (File.Exists(Path.Combine(baseImage, "index.json"))) "base image unavailable"
|
||||
|
||||
let executableMode =
|
||||
UnixFileMode.UserRead
|
||||
||| UnixFileMode.UserWrite
|
||||
||| UnixFileMode.UserExecute
|
||||
||| UnixFileMode.GroupRead
|
||||
||| UnixFileMode.GroupExecute
|
||||
||| UnixFileMode.OtherRead
|
||||
||| UnixFileMode.OtherExecute
|
||||
|
||||
let environment (architecture: string) =
|
||||
[ "CGO_ENABLED", "0"
|
||||
"GOCACHE", "/build-cache"
|
||||
"GOENV", "off"
|
||||
"GOOS", "linux"
|
||||
"GOARCH", architecture
|
||||
"GOMODCACHE", "/toolchain/modules"
|
||||
"GOPATH", "/toolchain/gopath"
|
||||
"GOPROXY", "off"
|
||||
"GOROOT", "/toolchain/usr/lib/go"
|
||||
"HOME", "/tmp" ]
|
||||
|
||||
let build (architecture: string) =
|
||||
let binary =
|
||||
Path.Combine(output, "bin", architecture, "project-delivery-controller")
|
||||
|
||||
directory (Path.GetDirectoryName binary) |> ignore
|
||||
|
||||
requireSuccess
|
||||
go
|
||||
[ "build"
|
||||
"-mod=readonly"
|
||||
"-trimpath"
|
||||
"-ldflags=-s -w -buildid="
|
||||
"-o"
|
||||
binary
|
||||
"." ]
|
||||
(Some source)
|
||||
(environment architecture)
|
||||
|> ignore
|
||||
|
||||
File.SetUnixFileMode(binary, executableMode)
|
||||
let header = File.ReadAllBytes binary |> Array.truncate 20
|
||||
let machine = if architecture = "amd64" then 62us else 183us
|
||||
|
||||
require
|
||||
(header.Length = 20
|
||||
&& header[0..3] = [| 0x7fuy; 0x45uy; 0x4cuy; 0x46uy |]
|
||||
&& header[4..5] = [| 2uy; 1uy |]
|
||||
&& BitConverter.ToUInt16(header, 18) = machine)
|
||||
$"invalid {architecture} executable"
|
||||
|
||||
binary
|
||||
|
||||
let copyTree (sourceRoot: string) (targetRoot: string) =
|
||||
if Directory.Exists targetRoot then
|
||||
Directory.Delete(targetRoot, true)
|
||||
|
||||
directory targetRoot |> ignore
|
||||
|
||||
Directory.EnumerateDirectories(sourceRoot, "*", SearchOption.AllDirectories)
|
||||
|> Seq.iter (fun path ->
|
||||
directory (Path.Combine(targetRoot, Path.GetRelativePath(sourceRoot, path)))
|
||||
|> ignore)
|
||||
|
||||
Directory.EnumerateFiles(sourceRoot, "*", SearchOption.AllDirectories)
|
||||
|> Seq.iter (fun path -> File.Copy(path, Path.Combine(targetRoot, Path.GetRelativePath(sourceRoot, path))))
|
||||
|
||||
let node (path: string) =
|
||||
let value = JsonNode.Parse(File.ReadAllText path)
|
||||
require (not <| isNull value) $"invalid JSON: {path}"
|
||||
value
|
||||
|
||||
let text (value: JsonNode) = value.GetValue<string>()
|
||||
|
||||
let digestPath (root: string) (digest: string) =
|
||||
require (validDigest digest) $"invalid OCI digest: {digest}"
|
||||
Path.Combine(root, digest[7..])
|
||||
|
||||
let blob (bytes: byte array) =
|
||||
let digest = SHA256.HashData bytes |> Convert.ToHexString |> _.ToLowerInvariant()
|
||||
File.WriteAllBytes(Path.Combine(layout, "blobs/sha256", digest), bytes)
|
||||
$"sha256:{digest}", int64 bytes.Length
|
||||
|
||||
let createLayer (architecture: string) (binary: string) =
|
||||
let tarPath = Path.Combine(output, $"{architecture}.tar")
|
||||
let gzipPath = tarPath + ".gz"
|
||||
|
||||
use tarOutput = File.Create tarPath
|
||||
use writer = new TarWriter(tarOutput, TarEntryFormat.Pax, true)
|
||||
let entry = PaxTarEntry(TarEntryType.RegularFile, "project-delivery-controller")
|
||||
entry.Gid <- 0
|
||||
entry.Uid <- 0
|
||||
entry.Mode <- executableMode
|
||||
entry.ModificationTime <- DateTimeOffset.UnixEpoch
|
||||
entry.DataStream <- File.OpenRead binary
|
||||
writer.WriteEntry entry
|
||||
entry.DataStream.Dispose()
|
||||
writer.Dispose()
|
||||
tarOutput.Dispose()
|
||||
let diff = sha256 tarPath
|
||||
|
||||
use input = File.OpenRead tarPath
|
||||
use compressed = File.Create gzipPath
|
||||
use encoder = new GZipStream(compressed, CompressionLevel.SmallestSize, true)
|
||||
input.CopyTo encoder
|
||||
encoder.Dispose()
|
||||
compressed.Dispose()
|
||||
let digest = sha256 gzipPath
|
||||
File.Delete tarPath
|
||||
diff, digest, FileInfo(gzipPath).Length, gzipPath
|
||||
|
||||
let addLayer (architecture: string) (binary: string) =
|
||||
let index = node (Path.Combine(layout, "index.json"))
|
||||
|
||||
let descriptor =
|
||||
index["manifests"].AsArray()
|
||||
|> Seq.find (fun item -> text (item["platform"]["architecture"]) = architecture)
|
||||
|
||||
let blobs = Path.Combine(layout, "blobs/sha256")
|
||||
let manifestPath = digestPath blobs (text (descriptor["digest"]))
|
||||
let manifest = node manifestPath
|
||||
let configDescriptor = manifest["config"]
|
||||
let config = node (digestPath blobs (text (configDescriptor["digest"])))
|
||||
let diff, digest, size, layerPath = createLayer architecture binary
|
||||
File.Move(layerPath, Path.Combine(blobs, digest), true)
|
||||
let layer = JsonObject()
|
||||
layer["mediaType"] <- "application/vnd.oci.image.layer.v1.tar+gzip"
|
||||
layer["digest"] <- $"sha256:{digest}"
|
||||
layer["size"] <- size
|
||||
manifest["layers"].AsArray().Add(layer)
|
||||
let diffId = $"sha256:{diff}"
|
||||
let rootfs = config["rootfs"]
|
||||
let diffIds = rootfs["diff_ids"].AsArray()
|
||||
diffIds.Add(diffId)
|
||||
|
||||
let configDigest, configSize =
|
||||
JsonSerializer.SerializeToUtf8Bytes(config, json) |> blob
|
||||
|
||||
configDescriptor["digest"] <- configDigest
|
||||
configDescriptor["size"] <- configSize
|
||||
|
||||
let manifestDigest, manifestSize =
|
||||
JsonSerializer.SerializeToUtf8Bytes(manifest, json) |> blob
|
||||
|
||||
descriptor["digest"] <- manifestDigest
|
||||
descriptor["size"] <- manifestSize
|
||||
File.WriteAllBytes(Path.Combine(layout, "index.json"), JsonSerializer.SerializeToUtf8Bytes(index, json))
|
||||
|
||||
let nameIndex () =
|
||||
let indexPath = Path.Combine(layout, "index.json")
|
||||
let index = node indexPath
|
||||
let nested = JsonObject()
|
||||
nested["schemaVersion"] <- 2
|
||||
nested["mediaType"] <- "application/vnd.oci.image.index.v1+json"
|
||||
nested["manifests"] <- index["manifests"].DeepClone()
|
||||
let digest, size = JsonSerializer.SerializeToUtf8Bytes(nested, json) |> blob
|
||||
let descriptor = JsonObject()
|
||||
descriptor["mediaType"] <- "application/vnd.oci.image.index.v1+json"
|
||||
descriptor["digest"] <- digest
|
||||
descriptor["size"] <- size
|
||||
let annotations = JsonObject()
|
||||
annotations["org.opencontainers.image.ref.name"] <- "delivery"
|
||||
descriptor["annotations"] <- annotations
|
||||
index["manifests"] <- JsonArray(descriptor)
|
||||
File.WriteAllBytes(indexPath, JsonSerializer.SerializeToUtf8Bytes(index, json))
|
||||
File.WriteAllText(Path.Combine(output, "image-digest"), digest + "\n")
|
||||
digest
|
||||
|
||||
copyTree baseImage layout
|
||||
|
||||
[ "amd64"; "arm64" ]
|
||||
|> List.iter (fun architecture -> build architecture |> addLayer architecture)
|
||||
|
||||
printfn "build=image digest=%s" (nameIndex ())
|
||||
Loading…
Reference in a new issue