From fc8b007ca1e0e6eaf73087c88eadb35967b68192 Mon Sep 17 00:00:00 2001 From: stackedsax Date: Sat, 18 Jul 2026 13:32:32 -0700 Subject: [PATCH] Generate a JSON Schema for SPLAT from the Go types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add internal/splat/schema, which reflects splat.Job into a JSON Schema (draft 2020-12) with the opaque scalar wrappers (Quantity, Duration) mapped to strings. The committed schema/splat.schema.json is regenerated with `make schema` (go test ./internal/splat/schema -update); a drift test runs under `go test ./...` so the schema can never silently fall out of sync with the types. Depends on github.com/invopop/jsonschema (kept out of the core splat package via the reflector's type Mapper). Note: validating the current examples/ against this schema fails because they use snake_case field names while the types/schema use camelCase — a pre-existing docs/impl mismatch tracked separately; the schema here reflects what the tool actually accepts. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016PnTz6Zxqa4jHocK8kCbyx --- Makefile | 6 +- README.md | 1 + go.mod | 5 + go.sum | 14 +- internal/splat/schema/schema.go | 60 ++ internal/splat/schema/schema_test.go | 76 +++ schema/splat.schema.json | 784 +++++++++++++++++++++++++++ 7 files changed, 943 insertions(+), 3 deletions(-) create mode 100644 internal/splat/schema/schema.go create mode 100644 internal/splat/schema/schema_test.go create mode 100644 schema/splat.schema.json diff --git a/Makefile b/Makefile index afe46c8..036ed5c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test lint tidy vuln check install clean help validate-schemas corpus dryrun-k8s dryrun-slurm +.PHONY: build test lint tidy vuln check install clean help validate-schemas schema corpus dryrun-k8s dryrun-slurm BINARY := bammm CMD := ./cmd/bammm @@ -39,6 +39,10 @@ vuln: validate-schemas: ./scripts/validate-schemas.sh +## schema: regenerate schema/splat.schema.json from the Go types +schema: + go test ./internal/splat/schema -update + ## corpus: scrape a scheduler corpus from GitHub (usage: make corpus SCHED=slurm; SCHED=pairs hunts cross-scheduler pairs) corpus: uv run scripts/corpus/fetch_corpus.py $(SCHED) diff --git a/README.md b/README.md index 35e267e..6349f46 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ make test # go test -race ./... make lint # golangci-lint make check # lint + test make validate-schemas # kubeconform Tier 2 schema validation +make schema # regenerate schema/splat.schema.json from the Go types make dryrun-k8s # Tier 3: kubectl --dry-run=server (needs a cluster + operators) make dryrun-slurm # Tier 3: sbatch --test-only (needs slurmctld) ``` diff --git a/go.mod b/go.mod index 55a6a4f..7f8ce22 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,14 @@ require ( ) require ( + github.com/invopop/jsonschema v0.14.0 k8s.io/api v0.31.3 k8s.io/apimachinery v0.31.3 ) require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -24,8 +27,10 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/text v0.16.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3bdd2ba..ba480e7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -17,6 +21,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -30,6 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -42,12 +50,14 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/splat/schema/schema.go b/internal/splat/schema/schema.go new file mode 100644 index 0000000..e6870ab --- /dev/null +++ b/internal/splat/schema/schema.go @@ -0,0 +1,60 @@ +// Package schema generates the JSON Schema for a SPLAT Job directly from the Go +// types in internal/splat, so the published schema/splat.schema.json can never +// silently drift from the code. Regenerate with: +// +// go test ./internal/splat/schema -update +package schema + +import ( + "bytes" + "encoding/json" + "reflect" + + "github.com/invopop/jsonschema" + + "github.com/InsightSoftmax/BAMMM/internal/splat" +) + +// SchemaID is the canonical $id of the SPLAT JSON Schema. +const SchemaID = "https://bammm.io/schemas/splat/v1alpha1.json" + +// Generate reflects splat.Job into an indented JSON Schema document. +func Generate() ([]byte, error) { + r := &jsonschema.Reflector{ + // The opaque scalar wrappers marshal to strings; reflection alone would + // emit empty objects for them (they have only unexported fields). + Mapper: func(t reflect.Type) *jsonschema.Schema { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + switch t { + case reflect.TypeOf(splat.Quantity{}): + return &jsonschema.Schema{ + Type: "string", + Description: `Resource quantity, e.g. "4Gi", "4G", "4096M", "4096" (bare = MB), or "500m" (CPU millicores).`, + } + case reflect.TypeOf(splat.Duration{}): + return &jsonschema.Schema{ + Type: "string", + Description: `Duration as ISO 8601 (e.g. PT2H30M), HH:MM:SS, or a plain number of seconds.`, + } + } + return nil + }, + } + + s := r.Reflect(&splat.Job{}) + s.ID = jsonschema.ID(SchemaID) + s.Title = "SPLAT Job" + s.Description = "Scheduler-Portable Language for Abstracting Tasks — the BAMMM interchange job spec " + + "(apiVersion bammm.io/v1alpha1). Generated from the Go types in internal/splat; see SPEC.md." + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetIndent("", " ") + enc.SetEscapeHTML(false) + if err := enc.Encode(s); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/internal/splat/schema/schema_test.go b/internal/splat/schema/schema_test.go new file mode 100644 index 0000000..bba186c --- /dev/null +++ b/internal/splat/schema/schema_test.go @@ -0,0 +1,76 @@ +package schema + +import ( + "bytes" + "encoding/json" + "flag" + "os" + "testing" +) + +var update = flag.Bool("update", false, "regenerate schema/splat.schema.json") + +// schemaPath is the committed schema, relative to this package directory. +const schemaPath = "../../../schema/splat.schema.json" + +// TestSchemaMatchesTypes regenerates the schema from the Go types and, unless +// -update is set, fails if the committed file is out of date — the CI drift gate. +func TestSchemaMatchesTypes(t *testing.T) { + got, err := Generate() + if err != nil { + t.Fatalf("Generate: %v", err) + } + + if *update { + if err := os.MkdirAll("../../../schema", 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(schemaPath, got, 0o644); err != nil { + t.Fatal(err) + } + t.Logf("wrote %s", schemaPath) + return + } + + want, err := os.ReadFile(schemaPath) + if err != nil { + t.Fatalf("read %s (generate it with `go test ./internal/splat/schema -update`): %v", schemaPath, err) + } + if !bytes.Equal(got, want) { + t.Errorf("schema/splat.schema.json is stale — regenerate with `go test ./internal/splat/schema -update`") + } +} + +// TestSchemaShape sanity-checks that reflection produced a usable schema. +func TestSchemaShape(t *testing.T) { + data, err := Generate() + if err != nil { + t.Fatalf("Generate: %v", err) + } + var doc map[string]any + if err := json.Unmarshal(data, &doc); err != nil { + t.Fatalf("generated schema is not valid JSON: %v", err) + } + if doc["$id"] != SchemaID { + t.Errorf("$id: got %v want %q", doc["$id"], SchemaID) + } + // The Job type reflects to a $ref into $defs; ensure the definitions exist + // and carry the top-level SPLAT fields. + defs, ok := doc["$defs"].(map[string]any) + if !ok { + t.Fatal("no $defs in generated schema") + } + job, ok := defs["Job"].(map[string]any) + if !ok { + t.Fatal("no Job definition in $defs") + } + props, ok := job["properties"].(map[string]any) + if !ok { + t.Fatal("Job has no properties") + } + for _, field := range []string{"apiVersion", "kind", "metadata", "spec"} { + if _, ok := props[field]; !ok { + t.Errorf("Job schema missing property %q", field) + } + } +} diff --git a/schema/splat.schema.json b/schema/splat.schema.json new file mode 100644 index 0000000..db9b5e9 --- /dev/null +++ b/schema/splat.schema.json @@ -0,0 +1,784 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bammm.io/schemas/splat/v1alpha1.json", + "$ref": "#/$defs/Job", + "$defs": { + "Array": { + "properties": { + "indices": { + "type": "string" + }, + "maxConcurrent": { + "type": "integer" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ContainerExecution": { + "properties": { + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "imagePullSecrets": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "environment": { + "$ref": "#/$defs/Environment" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Dependency": { + "properties": { + "scheme": { + "type": "string" + }, + "value": { + "type": "string" + }, + "count": { + "type": "integer" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "scheme" + ] + }, + "EmbeddedFile": { + "properties": { + "name": { + "type": "string" + }, + "content": { + "type": "string" + }, + "encoding": { + "type": "string" + }, + "permissions": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "content" + ] + }, + "EnvFromConfigMap": { + "properties": { + "name": { + "type": "string" + }, + "configMapName": { + "type": "string" + }, + "configMapKey": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "configMapName", + "configMapKey" + ] + }, + "EnvFromSecret": { + "properties": { + "name": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretKey": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "secretName", + "secretKey" + ] + }, + "Environment": { + "properties": { + "vars": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "secrets": { + "items": { + "$ref": "#/$defs/EnvFromSecret" + }, + "type": "array" + }, + "configMaps": { + "items": { + "$ref": "#/$defs/EnvFromConfigMap" + }, + "type": "array" + }, + "inheritFromSubmitter": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "EventPolicy": { + "properties": { + "event": { + "type": "string" + }, + "events": { + "items": { + "type": "string" + }, + "type": "array" + }, + "action": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Execution": { + "properties": { + "container": { + "$ref": "#/$defs/ContainerExecution" + }, + "script": { + "type": "string" + }, + "executable": { + "type": "string" + }, + "arguments": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "workingDir": { + "type": "string" + }, + "environment": { + "$ref": "#/$defs/Environment" + }, + "stdin": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Extensions": { + "properties": { + "slurm": { + "type": "object" + }, + "pbs": { + "type": "object" + }, + "lsf": { + "type": "object" + }, + "htcondor": { + "type": "object" + }, + "flux": { + "type": "object" + }, + "armada": { + "type": "object" + }, + "volcano": { + "type": "object" + }, + "kueue": { + "type": "object" + }, + "yunikorn": { + "type": "object" + }, + "runai": { + "type": "object" + } + }, + "additionalProperties": false, + "type": "object" + }, + "FileStaging": { + "properties": { + "inputs": { + "items": { + "$ref": "#/$defs/FileTransfer" + }, + "type": "array" + }, + "outputs": { + "items": { + "$ref": "#/$defs/FileTransfer" + }, + "type": "array" + }, + "embeddedFiles": { + "items": { + "$ref": "#/$defs/EmbeddedFile" + }, + "type": "array" + }, + "transferPolicy": { + "type": "string" + }, + "checkpointFiles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "checkpointExitCode": { + "type": "integer" + } + }, + "additionalProperties": false, + "type": "object" + }, + "FileTransfer": { + "properties": { + "src": { + "type": "string" + }, + "dst": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "src", + "dst" + ] + }, + "GPURequest": { + "properties": { + "count": { + "type": "number" + }, + "type": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "fraction": { + "type": "number" + }, + "migProfile": { + "type": "string" + }, + "exclusive": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Gang": { + "properties": { + "minAvailable": { + "type": "integer" + }, + "style": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Job": { + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "$ref": "#/$defs/Metadata" + }, + "spec": { + "$ref": "#/$defs/Spec" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ] + }, + "Lifecycle": { + "properties": { + "maxRetries": { + "type": "integer" + }, + "requeueOnFailure": { + "type": "boolean" + }, + "successExitCodes": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "ttlAfterFinished": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Metadata": { + "properties": { + "name": { + "type": "string" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "clientId": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ] + }, + "NFSVol": { + "properties": { + "server": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "server", + "path" + ] + }, + "Notifications": { + "properties": { + "email": { + "type": "string" + }, + "events": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Output": { + "properties": { + "stdout": { + "type": "string" + }, + "stderr": { + "type": "string" + }, + "mergeStderr": { + "type": "boolean" + }, + "openMode": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Placement": { + "properties": { + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "tolerations": { + "items": true, + "type": "array" + }, + "affinity": true, + "topologySpread": { + "items": true, + "type": "array" + }, + "topology": { + "type": "string" + }, + "groupBy": { + "type": "string" + }, + "constraint": { + "type": "string" + }, + "prefer": { + "type": "string" + }, + "exclusive": { + "type": "boolean" + }, + "nodePools": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ResourceLimits": { + "properties": { + "cpusPerTask": { + "type": "integer" + }, + "memoryPerTask": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Resources": { + "properties": { + "nodes": { + "type": "integer" + }, + "tasks": { + "type": "integer" + }, + "tasksPerNode": { + "type": "integer" + }, + "tasksPerSocket": { + "type": "integer" + }, + "tasksPerCore": { + "type": "integer" + }, + "cpusPerTask": { + "type": "integer" + }, + "memoryPerTask": { + "type": "string" + }, + "memoryPerCpu": { + "type": "string" + }, + "gpu": { + "$ref": "#/$defs/GPURequest" + }, + "diskPerTask": { + "type": "string" + }, + "genericResources": { + "additionalProperties": { + "type": "integer" + }, + "type": "object" + }, + "limits": { + "$ref": "#/$defs/ResourceLimits" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Schedule": { + "properties": { + "queue": { + "type": "string" + }, + "partition": { + "type": "string" + }, + "priorityClass": { + "type": "string" + }, + "priority": { + "type": "integer" + }, + "account": { + "type": "string" + }, + "project": { + "type": "string" + }, + "bank": { + "type": "string" + }, + "qos": { + "type": "string" + }, + "walltime": { + "type": "string" + }, + "walltimeMin": { + "type": "string" + }, + "beginAfter": { + "type": "string", + "format": "date-time" + }, + "deadline": { + "type": "string", + "format": "date-time" + }, + "signalBeforeEnd": { + "type": "string" + }, + "hold": { + "type": "boolean" + }, + "reservation": { + "type": "string" + }, + "exclusive": { + "type": "boolean" + }, + "exclusiveMode": { + "type": "string" + }, + "oversubscribe": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Spec": { + "properties": { + "schedule": { + "$ref": "#/$defs/Schedule" + }, + "resources": { + "$ref": "#/$defs/Resources" + }, + "execution": { + "$ref": "#/$defs/Execution" + }, + "tasks": { + "items": { + "$ref": "#/$defs/Task" + }, + "type": "array" + }, + "gang": { + "$ref": "#/$defs/Gang" + }, + "array": { + "$ref": "#/$defs/Array" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/Dependency" + }, + "type": "array" + }, + "lifecycle": { + "$ref": "#/$defs/Lifecycle" + }, + "placement": { + "$ref": "#/$defs/Placement" + }, + "output": { + "$ref": "#/$defs/Output" + }, + "notifications": { + "$ref": "#/$defs/Notifications" + }, + "fileStaging": { + "$ref": "#/$defs/FileStaging" + }, + "volumes": { + "items": { + "$ref": "#/$defs/Volume" + }, + "type": "array" + }, + "workloadType": { + "type": "string" + }, + "distributedFramework": { + "type": "string" + }, + "extensions": { + "$ref": "#/$defs/Extensions" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Task": { + "properties": { + "name": { + "type": "string" + }, + "replicas": { + "type": "integer" + }, + "minReplicas": { + "type": "integer" + }, + "resources": { + "$ref": "#/$defs/Resources" + }, + "execution": { + "$ref": "#/$defs/Execution" + }, + "lifecycle": { + "$ref": "#/$defs/TaskLifecycle" + }, + "placement": { + "$ref": "#/$defs/Placement" + }, + "dependsOn": { + "items": { + "$ref": "#/$defs/TaskDep" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ] + }, + "TaskDep": { + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ] + }, + "TaskLifecycle": { + "properties": { + "maxRetries": { + "type": "integer" + }, + "policies": { + "items": { + "$ref": "#/$defs/EventPolicy" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Volume": { + "properties": { + "name": { + "type": "string" + }, + "mountPath": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "pvc": { + "type": "string" + }, + "configMap": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "hostPath": { + "type": "string" + }, + "emptyDir": { + "type": "boolean" + }, + "nfs": { + "$ref": "#/$defs/NFSVol" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "mountPath" + ] + } + }, + "title": "SPLAT Job", + "description": "Scheduler-Portable Language for Abstracting Tasks — the BAMMM interchange job spec (apiVersion bammm.io/v1alpha1). Generated from the Go types in internal/splat; see SPEC.md." +}