ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c
18 changed files with 145336 additions and 0 deletions

View file

@ -0,0 +1,137 @@
#load "cachedata.fsx"
#load "image.fsx"
#load "catalog.fsx"
open System
open System.IO
open Contract
open Projects
open Image
open Cachedata
let revision = Environment.GetEnvironmentVariable "SOURCE_REVISION"
let targets = Environment.GetEnvironmentVariable "TARGETS" |> decode<Target array>
let project = selectProject targets
require (validRevision revision) "invalid source revision"
let sourceRoot = "/source/repository"
validateProject sourceRoot project
restoreCache "/cache" "/output/go-cache"
let work = "/workspace"
Directory.EnumerateDirectories("/toolchain/source", "*", SearchOption.AllDirectories)
|> Seq.iter (fun path ->
directory (Path.Combine(work, Path.GetRelativePath("/toolchain/source", path)))
|> ignore)
Directory.EnumerateFiles("/toolchain/source", "*", SearchOption.AllDirectories)
|> Seq.iter (fun path -> File.Copy(path, Path.Combine(work, Path.GetRelativePath("/toolchain/source", path))))
patches
|> List.iter (fun name ->
requireSuccess
"/toolchain/usr/bin/patch"
[ "--batch"
"--fuzz=0"
"--forward"
"--strip=1"
"--input=" + Path.Combine(sourceRoot, "packages/quay-operator", name) ]
(Some work)
[]
|> ignore)
let go = "/toolchain/usr/lib/go/bin/go"
let environment arch =
[ "CGO_ENABLED", "0"
"GOOS", "linux"
"GOARCH", arch
"GOENV", "off"
"GOTOOLCHAIN", "local"
"GOROOT", "/toolchain/usr/lib/go"
"GOCACHE", "/output/go-cache"
"GOPATH", "/tmp/gopath"
"GOPROXY", "off"
"GOWORK", "off" ]
let version = requireSuccess go [ "version" ] None (environment "amd64")
require (version.StartsWith("go version go1.27.3 ", StringComparison.Ordinal)) "Go version changed"
printfn "%s" (version.Trim())
let tests =
run go [ "test"; "-mod=vendor"; "./apis/quay/v1"; "./pkg/..." ] (Some work) (environment "amd64")
printfn "%s" (tests.Output.Trim())
require (tests.Code = 0) ("operator tests failed: " + tests.Error)
let assets =
Directory.EnumerateFiles(Path.Combine(work, "kustomize"), "*", SearchOption.AllDirectories)
|> Seq.map (fun path -> "workspace/" + Path.GetRelativePath(work, path), path)
|> Seq.toList
let files arch =
let binary = Path.Combine(directory ("/output/bin/" + arch), "manager")
// runtime.Caller locates Kustomize assets under the compiled /workspace path.
requireSuccess
go
[ "build"; "-mod=vendor"; "-ldflags=-s -w -buildid="; "-o"; binary; "main.go" ]
(Some work)
(environment arch)
|> ignore
("workspace/manager", binary) :: assets
let digest =
assemble "/toolchain/image-base" "/output" revision [ "/workspace/manager" ] "/workspace" files
printfn "build=image project=%s digest=%s" project.Name digest
let catalogBinary arch =
let binary = Path.Combine(directory ("/output/catalog-bin/" + arch), "opm")
requireSuccess
go
[ "build"
"-mod=readonly"
"-tags=json1,containers_image_openpgp"
"-trimpath"
"-ldflags=-s -w -buildid= -X github.com/operator-framework/operator-registry/cmd/opm/version.opmVersion="
+ catalogVersion
"-o"
binary
"./cmd/opm" ]
(Some "/toolchain/catalog-source")
(environment arch @ [ "GOMODCACHE", "/toolchain/modules" ])
|> ignore
File.SetUnixFileMode(binary, executableMode)
arch, binary
let catalogBinaries = [ catalogBinary "amd64"; catalogBinary "arm64" ] |> Map.ofList
let catalogProbe arch =
let binary =
Path.Combine(directory ("/output/catalog-bin/" + arch), "grpc_health_probe")
requireSuccess
go
[ "build"
"-mod=readonly"
"-trimpath"
"-ldflags=-s -w -buildid= -X main.versionTag=" + probeVersion
"-o"
binary
"." ]
(Some("/toolchain/modules/github.com/grpc-ecosystem/grpc-health-probe@" + probeVersion))
(environment arch @ [ "GOMODCACHE", "/toolchain/modules" ])
|> ignore
File.SetUnixFileMode(binary, executableMode)
arch, binary
let catalogProbes = [ catalogProbe "amd64"; catalogProbe "arm64" ] |> Map.ofList
let catalogDigest =
Catalog.build "/toolchain/catalog-base" catalogBinaries catalogProbes sourceRoot "/output" revision digest
File.WriteAllText("/output/catalog-digest", catalogDigest)
printfn "build=image project=quay-catalog digest=%s" catalogDigest

View file

@ -0,0 +1,156 @@
#r "System.Formats.Tar"
#r "/opt/fsi/packages/AWSSDK.Core.dll"
#r "/opt/fsi/packages/AWSSDK.S3.dll"
#r "/opt/fsi/packages/ZstdSharp.dll"
#nowarn "0760"
#load "cachedata.fsx"
#load "cache-gc.fsx"
open System
open System.Collections.Generic
open System.Formats.Tar
open System.IO
open System.Net
open System.Net.Http
open System.Net.Security
open System.Security.Cryptography
open System.Security.Cryptography.X509Certificates
open System.Text
open System.Text.Json
open System.Text.Json.Serialization
open System.Threading.Tasks
open Amazon.Runtime
open Amazon.S3
open Amazon.S3.Model
open Contract
open Projects
open Cachedata
open ZstdSharp
let bucket = "project-delivery"
let credential path = File.ReadAllText(path).Trim()
let root = X509CertificateLoader.LoadCertificateFromFile "/cache-ca/ca.crt"
let validateCertificate (certificate: X509Certificate) (providedChain: X509Chain) (errors: SslPolicyErrors) =
not (isNull certificate)
&& not (isNull providedChain)
&& not (errors.HasFlag SslPolicyErrors.RemoteCertificateNameMismatch)
&& (use chain = new X509Chain()
chain.ChainPolicy.TrustMode <- X509ChainTrustMode.CustomRootTrust
chain.ChainPolicy.CustomTrustStore.Add root |> ignore
chain.ChainPolicy.RevocationMode <- X509RevocationMode.NoCheck
providedChain.ChainElements
|> Seq.iter (fun item -> chain.ChainPolicy.ExtraStore.Add item.Certificate |> ignore)
chain.Build(new X509Certificate2(certificate)))
type CacheClientFactory() =
inherit HttpClientFactory()
override _.CreateHttpClient(_) =
let handler = new HttpClientHandler()
handler.ServerCertificateCustomValidationCallback <-
fun _ certificate chain errors -> validateCertificate certificate chain errors
new HttpClient(handler, true)
let configuration = AmazonS3Config()
configuration.ServiceURL <- "https://s3.quorate.internal"
configuration.AuthenticationRegion <- "us-east-1"
configuration.ForcePathStyle <- true
configuration.HttpClientFactory <- CacheClientFactory()
let client =
AmazonS3Client(
BasicAWSCredentials(credential "/cache-auth/accesskey", credential "/cache-auth/secretkey"),
configuration
)
let result (operation: Task<'T>) = operation.GetAwaiter().GetResult()
let project =
Environment.GetEnvironmentVariable "TARGETS"
|> decode<Target array>
|> selectProject
let prefix = $"projects/{project.Name}/{project.Workflow}/cache/go/"
let manifestKey = prefix + "_latest.json"
let pull () =
try
use reply =
result (client.GetObjectAsync(GetObjectRequest(BucketName = bucket, Key = manifestKey), cancellationToken))
use reader = new StreamReader(reply.ResponseStream)
let body = reader.ReadToEnd()
let manifest = decode<CacheManifest> body
if compatible manifest then
let digest = payloadDigest manifest
use payload =
result (
client.GetObjectAsync(
GetObjectRequest(BucketName = bucket, Key = prefix + manifest.PayloadKey),
cancellationToken
)
)
use output = File.Create "/cache/payload.tar.zst"
payload.ResponseStream.CopyTo output
output.Dispose()
require (sha256 "/cache/payload.tar.zst" = digest) "cache payload digest mismatch"
File.WriteAllText("/cache/manifest.json", body)
printfn "cache=downloaded component=go"
else
printfn "cache=miss component=go reason=incompatible"
with :? AmazonS3Exception as error when error.StatusCode = HttpStatusCode.NotFound ->
printfn "cache=miss component=go reason=absent"
let export () =
let archive = "/tmp/cache.tar.zst"
use output = File.Create archive
use zstd = new CompressionStream(output, 1)
use writer = new TarWriter(zstd, TarEntryFormat.Pax, true)
Directory.EnumerateFiles("/build-cache", "*", SearchOption.AllDirectories)
|> Seq.sort
|> Seq.iter (fun path -> writer.WriteEntry(path, Path.GetRelativePath("/build-cache", path)))
writer.Dispose()
zstd.Dispose()
output.Dispose()
let manifest =
{ SchemaVersion = 1
CacheVersion = cacheVersion ()
InputHashes = inputHashes ()
PayloadKey = $"objects/{sha256 archive}.tar.zst" }
use payload = File.OpenRead archive
result (
client.PutObjectAsync(
PutObjectRequest(BucketName = bucket, Key = prefix + manifest.PayloadKey, InputStream = payload),
cancellationToken
)
)
|> ignore
use body = new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(manifest, json))
let request =
PutObjectRequest(BucketName = bucket, Key = manifestKey, InputStream = body)
request.ContentType <- "application/json"
result (client.PutObjectAsync(request, cancellationToken)) |> ignore
CacheGc.prune client bucket manifestKey manifest.PayloadKey cancellationToken
printfn "cache=exported component=go"
match Environment.GetEnvironmentVariable "CACHE_ACTION" with
| "pull" -> pull ()
| "export" -> export ()
| _ -> fail "invalid cache action"

View file

@ -0,0 +1,119 @@
#r "/opt/fsi/packages/ZstdSharp.dll"
#load "projects.fsx"
open System
open System.Collections.Generic
open System.Formats.Tar
open System.IO
open System.Security.Cryptography
open System.Text
open System.Text.Json.Serialization
open Contract
open ZstdSharp
[<CLIMutable>]
type CacheManifest =
{ [<JsonPropertyName("schemaVersion")>]
SchemaVersion: int
[<JsonPropertyName("cacheVersion")>]
CacheVersion: string
[<JsonPropertyName("inputHashes")>]
InputHashes: Dictionary<string, string>
[<JsonPropertyName("payloadKey")>]
PayloadKey: string }
let cacheInputs =
[ "cachedata.fsx", "services/quay-operator/actions/cachedata.fsx"
"cache.fsx", "services/quay-operator/actions/cache.fsx"
"projects.fsx", "services/quay-operator/actions/projects.fsx"
"toolchain.fsx", "services/quay-operator/actions/toolchain.fsx"
"build.fsx", "services/quay-operator/actions/build.fsx"
"image.fsx", "services/project-delivery/actions/go-oci/image.fsx"
"catalog.fsx", "services/quay-operator/actions/catalog.fsx"
"contract.fsx", "services/project-delivery/actions/go-oci/contract.fsx"
"go-oci.apko.yaml", "services/quay-operator/actions/runtime.apko.yaml"
"catalog.apko.yaml", "services/quay-operator/actions/catalog.apko.yaml" ]
let cacheVersion () =
cacheInputs
|> List.map (fun (name, _) -> name + "=" + sha256 (Path.Combine("/orchestration", name)))
|> String.concat "\n"
|> Encoding.UTF8.GetBytes
|> SHA256.HashData
|> Convert.ToHexString
|> _.ToLowerInvariant()
let inputHashes () =
let hashes = Dictionary<string, string>()
for name, path in cacheInputs do
hashes[path] <- sha256 (Path.Combine("/orchestration", name))
hashes
let payloadDigest (manifest: CacheManifest) =
let key = manifest.PayloadKey
require
(not (isNull key)
&& key.StartsWith("objects/", StringComparison.Ordinal)
&& key.EndsWith(".tar.zst", StringComparison.Ordinal))
"invalid cache payload key"
let digest = key[8 .. key.Length - 9]
require (validSha256 digest && key = $"objects/{digest}.tar.zst") "invalid cache payload digest"
digest
let compatible (manifest: CacheManifest) =
let expected = inputHashes ()
manifest.SchemaVersion = 1
&& manifest.CacheVersion = cacheVersion ()
&& not (isNull manifest.InputHashes)
&& expected.Count = manifest.InputHashes.Count
&& expected
|> Seq.forall (fun pair -> manifest.InputHashes.TryGetValue pair.Key = (true, pair.Value))
let restoreCache cacheRoot destination =
directory destination |> ignore
let manifestPath = Path.Combine(cacheRoot, "manifest.json")
if File.Exists manifestPath then
let manifest = File.ReadAllText manifestPath |> decode<CacheManifest>
if compatible manifest then
let archive = Path.Combine(cacheRoot, "payload.tar.zst")
require (sha256 archive = payloadDigest manifest) "cache payload digest mismatch"
use input = File.OpenRead archive
use zstd = new DecompressionStream(input)
use reader = new TarReader(zstd)
let mutable entry = reader.GetNextEntry()
while not (isNull entry) do
let parts = entry.Name.TrimEnd('/').Split('/')
require
(parts
|> Array.forall (fun part -> part <> "" && part <> "." && part <> ".." && not (part.Contains '\\')))
"invalid cache path"
let target = Path.Combine(destination, entry.Name)
match entry.EntryType with
| TarEntryType.Directory -> directory target |> ignore
| TarEntryType.RegularFile
| TarEntryType.V7RegularFile ->
directory (Path.GetDirectoryName target) |> ignore
use output = new FileStream(target, FileMode.CreateNew)
if entry.Length > 0L then
entry.DataStream.CopyTo output
| _ -> fail "invalid cache entry type"
entry <- reader.GetNextEntry()
printfn "cache=restored component=go"
else
printfn "cache=miss component=go reason=incompatible"
else
printfn "cache=miss component=go reason=absent"

View file

@ -0,0 +1,20 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- ca-certificates-bundle=20261020-r0
- wolfi-baselayout=20230201-r31
accounts:
groups:
- groupname: application
gid: 65532
users:
- username: application
uid: 65532
gid: 65532
run-as: 65532
environment:
PATH: /usr/bin:/bin
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt

View file

@ -0,0 +1,80 @@
open System
open System.IO
open System.Runtime.InteropServices
open System.Text
open System.Text.Json.Nodes
open Contract
open Image
let build
basePath
(binaries: Map<string, string>)
(probes: Map<string, string>)
sourceRoot
output
revision
operatorDigest
=
let host =
match RuntimeInformation.ProcessArchitecture with
| Architecture.X64 -> "amd64"
| Architecture.Arm64 -> "arm64"
| value -> fail $"unsupported catalog builder architecture {value}"
let opm = binaries[host]
let source = Path.Combine(sourceRoot, "packages/project-quay-catalog")
let content =
requireSuccess opm [ "render"; Path.Combine(source, "bundles/3.18.2"); "--output"; "json" ] None []
let bundle = JsonNode.Parse content
let operatorImage = $"zot.quorate.internal/olm/quay-operator@{operatorDigest}"
for property in (bundle["properties"]).AsArray() do
if (property["type"]).GetValue<string>() = "olm.bundle.object" then
let bytes =
Convert.FromBase64String((property.["value"].["data"]).GetValue<string>())
let resource = JsonNode.Parse(Encoding.UTF8.GetString bytes)
if (resource["kind"]).GetValue<string>() = "ClusterServiceVersion" then
resource.["metadata"].["annotations"].["containerImage"] <- operatorImage
let deployment = resource.["spec"].["install"].["spec"].["deployments"].[0]
deployment.["spec"].["template"].["spec"].["containers"].[0].["image"] <- operatorImage
for image in (resource.["spec"].["relatedImages"]).AsArray() do
image["image"] <- operatorImage
property.["value"].["data"] <-
resource.ToJsonString() |> Encoding.UTF8.GetBytes |> Convert.ToBase64String
for image in (bundle["relatedImages"]).AsArray() do
image["image"] <- operatorImage
let package =
requireSuccess
opm
[ "alpha"
"render-template"
"basic"
Path.Combine(source, "catalog-template.yaml")
"--output"
"json" ]
None
[]
let config = directory (Path.Combine(output, "catalog-config"))
let catalog = Path.Combine(config, "catalog.json")
File.WriteAllText(catalog, bundle.ToJsonString() + "\n" + package)
requireSuccess opm [ "validate"; config ] None [] |> ignore
assemble
basePath
(Path.Combine(output, "catalog"))
revision
[ "/usr/bin/opm"; "serve"; "/configs" ]
"/"
(fun architecture ->
[ "usr/bin/opm", binaries[architecture]
"usr/bin/grpc_health_probe", probes[architecture]
"configs/catalog.json", catalog ])

View file

@ -0,0 +1,4 @@
#load "go-init.fsx"
System.IO.Directory.CreateDirectory "/workspace/output/workspace" |> ignore
System.IO.Directory.CreateDirectory "/workspace/cache" |> ignore

View file

@ -0,0 +1,45 @@
#load "contract.fsx"
open System.IO
open Contract
type Project =
{ Name: string
Workflow: string
Repository: string }
let upstream = "117e757e6abd2729d6ab4cfa93a2af674d47e1e4"
let sourceHash = "0d3cd9fbca03f5d4fd797a848eae3fda7c9725a895a897b656b2aee1de4c0fe8"
let patches = [ "status.patch" ]
let project =
{ Name = "quay-op"
Workflow = "quay-op-build"
Repository = "olm/quay-operator" }
let catalogRepo = "olm/project-quay-catalog"
let catalogVersion = "v1.74.0"
let catalogRef = "a936068280ee098fb8bcd089a1a9f14de12d0ece"
let catalogHash = "834966a48aecd6e6a6489b879c08c849d13418f86c813551140e6c7c866965e1"
let probeVersion = "v0.4.61"
let probeRef = "5d0402b1a167a1a610e51b458287c4bf12331bc6"
let sourcePaths =
(patches |> List.map (fun name -> "packages/quay-operator/" + name))
@ [ "packages/project-quay-catalog/catalog-template.yaml"
"packages/project-quay-catalog/bundles/3.18.2/manifests/quay-operator.clusterserviceversion.yaml"
"packages/project-quay-catalog/bundles/3.18.2/manifests/quayregistries.crd.yaml"
"packages/project-quay-catalog/bundles/3.18.2/metadata/annotations.yaml" ]
let selectProject (targets: Target array) =
require
(targets.Length = 2
&& (targets |> Array.forall (fun target -> target.Kind = "ZotOCI"))
&& (targets |> Array.map _.Repository |> Set.ofArray) = Set.ofList [ project.Repository; catalogRepo ])
"invalid operator delivery target"
project
let validateProject sourceRoot _ =
sourcePaths
|> List.iter (fun path -> require (File.Exists(Path.Combine(sourceRoot, path))) $"missing source input: {path}")

View file

@ -0,0 +1,55 @@
#nowarn "0760"
#r "/opt/fsi/packages/Oci.dll"
#load "projects.fsx"
open System
open System.IO
open System.Text.Json
open Contract
open Quorate.Oci
open Projects
let revision = Environment.GetEnvironmentVariable "SOURCE_REVISION"
let targets = Environment.GetEnvironmentVariable "TARGETS" |> decode<Target array>
let project = selectProject targets
let layout = "/handoff/layout"
let digest = File.ReadAllText("/handoff/image-digest").Trim()
let catalogDigest = File.ReadAllText("/handoff/catalog-digest").Trim()
require (validRevision revision) "invalid source revision"
require (validDigest digest) "invalid image digest"
require (validDigest catalogDigest) "invalid catalog digest"
let publish () =
let auth = RegistryAuth.Load("/auth/.dockerconfigjson", "zot.quorate.internal")
use root = Registry.LoadRoot "/registry-ca/ca.crt"
use registry = Registry(Uri("https://zot.quorate.internal"), auth, root)
[ project.Repository, layout, digest
catalogRepo, "/handoff/catalog/layout", catalogDigest ]
|> List.map (fun (repository, imageLayout, expectedDigest) ->
let published =
registry
.PushAsync(repository, imageLayout, "delivery", $"delivery-{revision}", cancellationToken)
.GetAwaiter()
.GetResult()
require (published = expectedDigest) "published digest mismatch"
printfn "publish=%s digest=%s" repository expectedDigest
{ Kind = "ZotOCI"
Repository = repository
Reference = $"zot.quorate.internal/{repository}@{expectedDigest}" })
let published = publish () |> List.toArray
directory "/result" |> ignore
File.WriteAllText(
"/result/delivery-result",
JsonSerializer.Serialize(
{ SourceRevision = revision
Targets = published },
json
)
)

View file

@ -0,0 +1,19 @@
contents:
keyring:
- https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
repositories:
- https://packages.wolfi.dev/os
packages:
- ca-certificates-bundle=20261020-r0
accounts:
groups:
- groupname: application
gid: 65532
users:
- username: application
uid: 65532
gid: 65532
run-as: 65532
environment:
PATH: /usr/bin:/bin
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt

View file

@ -0,0 +1,43 @@
#load "sourcefilter.fsx"
open System
open System.IO
open System.IO.Compression
open System.Net.Http
open System.Net.Http.Headers
open Contract
open Sourcefilter
let repository = Environment.GetEnvironmentVariable "SOURCE_REPOSITORY"
let revision = Environment.GetEnvironmentVariable "SOURCE_REVISION"
let archive = "/source/tmp/source.tar.gz"
let destination = "/source/repository"
require (repository = "https://forge.quorate.internal/quorate/cluster") "invalid source repository"
require (validRevision revision) "invalid source revision"
let token = File.ReadAllText("/source-auth/token").Trim()
let download () =
use request =
new HttpRequestMessage(
HttpMethod.Get,
$"http://forgejo-http.forgejo.svc.cluster.local.:3000/api/v1/repos/quorate/cluster/archive/{revision}.tar.gz"
)
request.Headers.Authorization <- AuthenticationHeaderValue("token", token)
use client = new HttpClient(Timeout = TimeSpan.FromMinutes 10.0)
use reply =
client.Send(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
require reply.IsSuccessStatusCode $"source download failed: {int reply.StatusCode}"
use input = reply.Content.ReadAsStream(cancellationToken)
use output = File.Create archive
input.CopyTo output
download ()
extractSource archive destination
File.Delete archive
File.WriteAllText("/source/.ready", revision + "\n")
printfn "source=primary revision=%s" revision

View file

@ -0,0 +1,33 @@
#load "projects.fsx"
open System.Collections.Generic
open System.Formats.Tar
open System.IO
open System.IO.Compression
open Contract
open Projects
let extractSource archive destination =
use input = File.OpenRead archive
use gzip = new GZipStream(input, CompressionMode.Decompress)
use reader = new TarReader(gzip)
let found = HashSet<string>()
let mutable root = None
let mutable entry = reader.GetNextEntry()
while not (isNull entry) do
if entry.EntryType <> TarEntryType.GlobalExtendedAttributes then
let parts = entry.Name.TrimEnd('/').Split('/')
require (parts |> Array.forall (fun part -> part <> "" && part <> "." && part <> ".." && not (part.Contains '\\'))) "invalid source path"
match root with
| None -> root <- Some parts[0]
| Some name -> require (name = parts[0]) "multiple source roots"
let path = parts |> Array.skip 1 |> String.concat "/"
if List.contains path sourcePaths then
require (entry.EntryType = TarEntryType.RegularFile || entry.EntryType = TarEntryType.V7RegularFile) "source input is not a regular file"
require (found.Add path) "duplicate source input"
let target = Path.Combine(destination, path)
directory (Path.GetDirectoryName target) |> ignore
use output = File.Create target
if entry.Length > 0L then entry.DataStream.CopyTo output
entry <- reader.GetNextEntry()
require (found.Count = sourcePaths.Length) "source inputs are missing"

View file

@ -0,0 +1,119 @@
#load "projects.fsx"
open System
open System.IO
open System.Net.Http
open Contract
open Projects
do
let root = "/toolchain"
requireSuccess
"/sbin/apk"
[ "add"
"--root"
root
"--initdb"
"--no-scripts"
"--keys-dir"
"/etc/apk/keys"
"--repository"
"https://packages.wolfi.dev/os"
"go-1.27=1.27.3-r0"
"patch=2.8.1-r1"
"ca-certificates-bundle=20261020-r0" ]
None
[]
|> ignore
let archive = "/tmp/operator.tar.gz"
use client = new HttpClient(Timeout = TimeSpan.FromMinutes 2.0)
use response =
client.GetAsync($"https://github.com/quay/quay-operator/archive/{upstream}.tar.gz").GetAwaiter().GetResult()
require response.IsSuccessStatusCode $"operator source HTTP {int response.StatusCode}"
use source = response.Content.ReadAsStream()
use target = File.Create archive
source.CopyTo target
target.Dispose()
require (sha256 archive = sourceHash) "operator source checksum mismatch"
let incoming = directory (Path.Combine(root, "incoming"))
extractGzip archive incoming
let fetched = Directory.EnumerateDirectories incoming |> Seq.exactlyOne
Directory.Move(fetched, Path.Combine(root, "source"))
let catalogArchive = "/tmp/catalog.tar.gz"
use catalogReply =
client
.GetAsync($"https://github.com/operator-framework/operator-registry/archive/{catalogRef}.tar.gz")
.GetAwaiter()
.GetResult()
require catalogReply.IsSuccessStatusCode $"catalog source HTTP {int catalogReply.StatusCode}"
use catalogInput = catalogReply.Content.ReadAsStream()
use catalogOutput = File.Create catalogArchive
catalogInput.CopyTo catalogOutput
catalogOutput.Dispose()
require (sha256 catalogArchive = catalogHash) "catalog source checksum mismatch"
let catalogIncoming = directory (Path.Combine(root, "catalog-incoming"))
extractGzip catalogArchive catalogIncoming
let catalogSource = Path.Combine(root, "catalog-source")
Directory.Move(Directory.EnumerateDirectories(catalogIncoming) |> Seq.exactlyOne, catalogSource)
let environment =
[ "GOENV", "off"
"GOTOOLCHAIN", "local"
"GOWORK", "off"
"GOROOT", Path.Combine(root, "usr/lib/go")
"GOPATH", "/tmp/gopath"
"GOMODCACHE", Path.Combine(root, "modules")
"GOPROXY", "https://proxy.golang.org"
"GOSUMDB", "sum.golang.org"
"GOCACHE", "/tmp/go-cache" ]
requireSuccess (Path.Combine(root, "usr/lib/go/bin/go")) [ "mod"; "download" ] (Some catalogSource) environment
|> ignore
requireSuccess
(Path.Combine(root, "usr/lib/go/bin/go"))
[ "mod"; "download"; "github.com/grpc-ecosystem/grpc-health-probe@" + probeRef ]
(Some root)
environment
|> ignore
let probeSource =
Path.Combine(root, "modules/github.com/grpc-ecosystem/grpc-health-probe@" + probeVersion)
requireSuccess (Path.Combine(root, "usr/lib/go/bin/go")) [ "mod"; "download" ] (Some probeSource) environment
|> ignore
requireSuccess
"/usr/bin/apko"
[ "build"
"--arch"
"x86_64,arm64"
"--sbom=false"
"/orchestration/go-oci.apko.yaml"
"quay-op:base"
directory (Path.Combine(root, "image-base")) ]
None
[]
|> ignore
requireSuccess
"/usr/bin/apko"
[ "build"
"--arch"
"x86_64,arm64"
"--sbom=false"
"/orchestration/catalog.apko.yaml"
"quay-catalog:base"
directory (Path.Combine(root, "catalog-base")) ]
None
[]
|> ignore
printfn "toolchain=primary upstream=%s" upstream

View file

@ -0,0 +1,3 @@
images:
- path: spec/templates/container/image
kind: WorkflowTemplate

View file

@ -0,0 +1,90 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: tools
namespace: project-delivery
spec:
endpointSelector:
matchLabels:
delivery.quorate.dev/stage: quay-op-toolchain
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- {port: "53", protocol: UDP}
- {port: "53", protocol: TCP}
rules:
dns:
- {matchName: packages.wolfi.dev}
- {matchName: github.com}
- {matchName: codeload.github.com}
- {matchName: proxy.golang.org}
- {matchName: sum.golang.org}
- {matchName: storage.googleapis.com}
- toFQDNs:
- {matchName: packages.wolfi.dev}
- {matchName: github.com}
- {matchName: codeload.github.com}
- {matchName: proxy.golang.org}
- {matchName: sum.golang.org}
- {matchName: storage.googleapis.com}
toPorts:
- ports:
- {port: "443", protocol: TCP}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: publish
namespace: project-delivery
spec:
endpointSelector:
matchLabels:
delivery.quorate.dev/stage: quay-op-publish
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- {port: "53", protocol: UDP}
- {port: "53", protocol: TCP}
rules:
dns:
- {matchName: zot.quorate.internal}
- toCIDR: [172.28.129.182/32]
toPorts:
- ports:
- {port: "443", protocol: TCP}
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: zot
app.kubernetes.io/name: zot
app.kubernetes.io/instance: zot
toPorts:
- ports:
- {port: "5000", protocol: TCP}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: zot-in
namespace: zot
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: zot
app.kubernetes.io/instance: zot
ingress:
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: project-delivery
io.cilium.k8s.policy.serviceaccount: project-delivery-executor
delivery.quorate.dev/stage: quay-op-publish
toPorts:
- ports:
- {port: "5000", protocol: TCP}

View file

@ -0,0 +1,21 @@
apiVersion: delivery.quorate.dev/v1alpha1
kind: ProjectDelivery
metadata:
name: image
namespace: project-delivery
spec:
source:
repository: https://forge.quorate.internal/quorate/cluster
continuous:
ref: refs/heads/master
paths:
- packages/quay-operator
- packages/project-quay-catalog
- services/quay-operator
targets:
- kind: ZotOCI
repository: olm/quay-operator
- kind: ZotOCI
repository: olm/project-quay-catalog
workflowTemplateRef:
name: build

View file

@ -0,0 +1,45 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: zot-vso
namespace: project-delivery
automountServiceAccountToken: false
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: zot
namespace: project-delivery
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: quay-op-writer
serviceAccount: quay-op-zot-vso
vaultConnectionRef: vault/vault
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: writer
namespace: project-delivery
spec:
type: kv-v2
mount: secret
path: zot/quay-op
refreshAfter: 1h
destination:
name: quay-op-writer
create: true
overwrite: true
type: kubernetes.io/dockerconfigjson
transformation:
excludeRaw: true
excludes: [.*]
templates:
.dockerconfigjson:
text: |
{{- $user := get .Secrets "username" -}}
{{- $password := get .Secrets "password" -}}
{"auths":{"zot.quorate.internal":{"username":"{{ $user }}","password":"{{ $password }}","auth":"{{ printf "%s:%s" $user $password | b64enc }}"}}}
vaultAuthRef: quay-op-zot

View file

@ -0,0 +1,80 @@
- op: replace
path: /metadata/labels/app.kubernetes.io~1instance
value: quay-op
- op: replace
path: /spec/podMetadata/labels/delivery.quorate.dev~1project
value: quay-op
- op: replace
path: /spec/templates/5/metadata/labels/delivery.quorate.dev~1stage
value: quay-op-toolchain
- op: replace
path: /spec/templates/7/metadata/labels/delivery.quorate.dev~1stage
value: quay-op-publish
- op: replace
path: /spec/templates/1/volumes/0
value: &orchestration
name: orchestration
projected:
defaultMode: 0444
sources:
- configMap:
name: go-oci-orch
items:
- {key: contract.fsx, path: contract.fsx}
- {key: initialize.fsx, path: go-init.fsx}
- {key: image.fsx, path: image.fsx}
- configMap:
name: quay-op-project
- op: replace
path: /spec/templates/3/volumes/0
value: *orchestration
- op: replace
path: /spec/templates/4/volumes/0
value: *orchestration
- op: replace
path: /spec/templates/5/volumes/0
value: *orchestration
- op: replace
path: /spec/templates/6/volumes/0
value: *orchestration
- op: replace
path: /spec/templates/7/volumes/0
value: *orchestration
- op: replace
path: /spec/templates/7/volumes/1/secret/secretName
value: quay-op-writer
- op: replace
path: /spec/templates/8/volumes/0
value: *orchestration
- op: add
path: /spec/templates/6/container/volumeMounts/-
value:
name: workflow-workspace
mountPath: /workspace
subPath: output/workspace
- op: replace
path: /spec/templates/4/container/volumeMounts
value:
- {name: orchestration, mountPath: /orchestration, readOnly: true}
- {name: cache-auth, mountPath: /cache-auth, readOnly: true}
- {name: cache-ca, mountPath: /cache-ca, readOnly: true}
- {name: workflow-workspace, mountPath: /cache, subPath: cache}
- {name: workflow-workspace, mountPath: /tmp, subPath: state/cache-tmp}
- op: replace
path: /spec/templates/5/container/volumeMounts
value:
- {name: orchestration, mountPath: /orchestration, readOnly: true}
- {name: workflow-workspace, mountPath: /toolchain, subPath: toolchain}
- {name: workflow-workspace, mountPath: /tmp, subPath: state/toolchain-tmp}
- op: add
path: /spec/templates/6/container/volumeMounts/5/readOnly
value: true
- op: replace
path: /spec/templates/8/container/volumeMounts
value:
- {name: orchestration, mountPath: /orchestration, readOnly: true}
- {name: cache-auth, mountPath: /cache-auth, readOnly: true}
- {name: cache-ca, mountPath: /cache-ca, readOnly: true}
- {name: workflow-workspace, mountPath: /source, subPath: source, readOnly: true}
- {name: workflow-workspace, mountPath: /build-cache, subPath: output/go-cache, readOnly: true}
- {name: workflow-workspace, mountPath: /tmp, subPath: output/cache-tmp}

View file

@ -0,0 +1,57 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: quay-op-
configurations:
- k8s/kustomizeconfig.yaml
images:
- name: registry.quorate.internal/quorate/fsharp-runner
digest: sha256:d7b904f4f0cae9fdba7c7345cdf08c91162245834a8a13658d93396272aeec83
resources:
- ../project-delivery/k8s/workflows/go-oci/parallel
- k8s/network-policy.yaml
- k8s/project.yaml
- k8s/vso.yaml
configMapGenerator:
- name: project
namespace: project-delivery
files:
- build.fsx=actions/build.fsx
- catalog.fsx=actions/catalog.fsx
- catalog.apko.yaml=actions/catalog.apko.yaml
- cache.fsx=actions/cache.fsx
- cache-gc.fsx=../project-delivery/actions/cache-gc.fsx
- cachedata.fsx=actions/cachedata.fsx
- source.fsx=actions/source.fsx
- sourcefilter.fsx=actions/sourcefilter.fsx
- initialize.fsx=actions/initialize.fsx
- projects.fsx=actions/projects.fsx
- publish.fsx=actions/publish.fsx
- toolchain.fsx=actions/toolchain.fsx
- go-oci.apko.yaml=actions/runtime.apko.yaml
generatorOptions:
disableNameSuffixHash: true
labels:
app.kubernetes.io/name: project-delivery
app.kubernetes.io/instance: quay-op
patches:
- path: k8s/workflow-patch.yaml
target:
group: argoproj.io
version: v1alpha1
kind: WorkflowTemplate
name: build
replacements:
- source:
group: argoproj.io
version: v1alpha1
kind: WorkflowTemplate
name: quay-op-build
fieldPath: metadata.name
targets:
- select:
group: delivery.quorate.dev
version: v1alpha1
kind: ProjectDelivery
name: quay-op-image
fieldPaths:
- spec.workflowTemplateRef.name