From e63806a9d5a7714471c99ceebe25c0969cc11e78 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 14 Feb 2024 01:07:49 +0000 Subject: [PATCH 1/8] Initial pass at modeling SPEC in cue, incomplete WIP --- cue/frontend.yml | 57 ++++++++++++++++++++++ cue/spec.cue | 120 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 cue/frontend.yml create mode 100644 cue/spec.cue diff --git a/cue/frontend.yml b/cue/frontend.yml new file mode 100644 index 000000000..f2498aa25 --- /dev/null +++ b/cue/frontend.yml @@ -0,0 +1,57 @@ +# syntax=ghcr.io/azure/dalec/frontend:latest + +name: dalec-rpm-frontend +description: A test fixture which builds this project as an RPM, mainly to validate generating sources from a command. +website: https://www.github.com/Azure/dalec +version: 0.0.1 +revision: 1 +vendor: Microsoft + +packager: Microsoft +license: Apache 2.0 +sources: + src: + path: /blah + context: + name: . + gomodcache: + path: /build/gomodcache + image: + ref: mcr.microsoft.com/oss/go/microsoft/golang:1.21 + cmd: + dir: /build/src + steps: + - command: go mod download + env: + GOMODCACHE: /build/gomodcache + mounts: + - dest: /build/src + spec: + context: + name: . + +dependencies: + build: + golang: [] + +build: + steps: + - command: | + export GOMODCACHE="$(pwd)/gomodcache" + cd src + go build -o ../frontend ./cmd/frontend + + env: + GOPROXY: direct + CGO_ENABLED: "0" + GOGC: off + GOFLAGS: -trimpath + GOPATH: /go + GOROOT: /usr/lib/golang + +artifacts: + binaries: + frontend: + +image: + entrypoint: frontend diff --git a/cue/spec.cue b/cue/spec.cue new file mode 100644 index 000000000..7dd0a7219 --- /dev/null +++ b/cue/spec.cue @@ -0,0 +1,120 @@ +package spec + + +#BuildStep: close({ + command: string + env?: [string]: string +}) + +#CacheDirConfig: close({ + // TODO: specify further + mode?: string + key?: string + include_distro_key?: bool + include_arch_key?: bool +}) + +#Command: close({ + dir?: string + mounts?: [#SourceMount] + cache_dirs?: [string]: #CacheDirConfig + env?: [string]: string + steps?: [#BuildStep] +}) + +#SourceMount: close({ + dest: string + // structural recursion must terminate + spec: null | #Source +}) + +#SourceContext: close({ + name: string +}) + +#SourceImage: close({ + ref?: string + cmd?: #Command +}) + +#SourceHTTP: close({ + // TODO: specify further + url?: string +}) + +#SourceBuild: close({ + source?: #Source + dockerfile?: string + inline?: string + target?: string + args: [string]: string +}) + +#SourceGit: close({ + url?: string + commit?: string + keepGitDir?: bool +}) + +#Source: close({ + path?: string + context?: #SourceContext + git?: #SourceGit + http?: #SourceHTTP + build?: #SourceBuild + image?: #SourceImage +}) + +#PackageDependencies: close({ + build?: [string]: ([] | [string]) + runtime?: [string]: ([] | [string]) + recommends?: [string]: ([] | [string]) + test?: [string]: ([] | [string]) +}) + +#ArtifactBuild: close({ + steps: [#BuildStep] + env?: [string]: string +}) + +#ArtifactConfig: close({ + name: string + subPath?: string +}) + +#Artifacts: close({ + binaries?: [string]: #ArtifactConfig | null + manpages?: [string]: #ArtifactConfig | null +}) + +#ImageConfig: close({ + entrypoint?: string + cmd?: string + env?: [string] + labels?: [string]: string + volumes?: [string]: null + working_dir?: string + stop_signal?: string + base?: string +}) + +// TODO: export somehow? +#Spec: close({ + name: string + description: string + website: string + version: string + revision: uint + vendor: string + packager: string + license: string + + sources: [string]: #Source + + dependencies: #PackageDependencies + build: #ArtifactBuild + artifacts: #Artifacts + image: #ImageConfig +}) + +#Spec \ No newline at end of file From 6ffd03b7be8330a1d6f37b8eee2591239431154a Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 6 Mar 2024 20:55:05 +0000 Subject: [PATCH 2/8] Add updated spec.cue --- cue/spec.cue | 298 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 227 insertions(+), 71 deletions(-) diff --git a/cue/spec.cue b/cue/spec.cue index 7dd0a7219..84c9cb182 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -1,120 +1,276 @@ package spec +import ( + "time" +) + +// further specify string fields that deal with urls? +// move under dalec/ +// slot in and run against all existing test specs + +// Create test harness +// Test harness should verify that cue spec catches errors for subfields +// particularly for things people are likely to make errors with + +// allow any filepath with no whitespace characters. can further restrict later +let filepath = =~ "^[^\\0\\s]+$" + +// {0, ..., 511} = {0o000, ..., 0o777} are valid unix perms +let perms = >= 0 & <= 0o777 + +// a source name must be alphanumeric, with the inclusion of '_' and '-' +// TODO: consider including '.' to allow sources with a file extension +// TEST: invalid source names +#sourceName: =~ "^[a-zA-Z0-9_-]+$" #BuildStep: close({ - command: string - env?: [string]: string + command: string //c + env?: [string]: string //c }) +// TEST: invalid cache keys #CacheDirConfig: close({ - // TODO: specify further - mode?: string - key?: string - include_distro_key?: bool - include_arch_key?: bool + mode?: "shared" | "private" | "locked" //c + key?: string //c + include_distro_key?: bool //c + include_arch_key?: bool //c }) #Command: close({ - dir?: string - mounts?: [#SourceMount] - cache_dirs?: [string]: #CacheDirConfig - env?: [string]: string - steps?: [#BuildStep] + dir?: filepath //c + mounts?: [#SourceMount] //c + cache_dirs?: [filepath]: #CacheDirConfig //c + env?: [string]: string //c + steps: [#BuildStep] //c }) +// TEST: structural cycle case #SourceMount: close({ - dest: string - // structural recursion must terminate - spec: null | #Source + dest: filepath //c + // structural cycle formed by source.image.mounts.spec.source must terminate somehow for cue to accept + // TODO: look into how this cycle could be terminated further up the chain, as this currently would allow + // a source mount with no provided spec which is invalid (though a significant edge case) + spec: null | #Source //c }) #SourceContext: close({ - name: string -}) + name?: string +}) //c -#SourceImage: close({ - ref?: string - cmd?: #Command +#SourceDockerImage: close({ //c + ref: string //c + cmd?: #Command //c }) #SourceHTTP: close({ - // TODO: specify further - url?: string + // TODO: specify url field further? + url: string //c }) #SourceBuild: close({ - source?: #Source - dockerfile?: string - inline?: string - target?: string - args: [string]: string + source?: #SubSource //c + dockerfile_path?: string //c + target?: string //c + args?: [string]: string //c }) #SourceGit: close({ - url?: string - commit?: string - keepGitDir?: bool + // TODO: specify URL field further? + url: string //c + commit?: string //c + keepGitDir?: bool //c }) -#Source: close({ - path?: string - context?: #SourceContext - git?: #SourceGit - http?: #SourceHTTP - build?: #SourceBuild - image?: #SourceImage +// TEST UID/GID < 0 +#SourceInlineFile: close({ + contents?: string //c + permissions?: perms //c + uid?: >= 0 //c + gid?: >= 0 //c }) + +// TEST UID/GID < 0 +#SourceInlineDir: close({ + files?: [string]: #SourceInlineFile //c + permissions?: perms //c + uid?: >= 0 //c + gid?: >= 0 //c +}) + +#SourceInline: { file: #SourceInlineFile } | + { dir: #SourceInlineDir } //c + +#SourceVariant: { context: #SourceContext } | + { git: #SourceGit } | + { build: #SourceBuild } | + { http: #SourceHTTP } | + { image: #SourceDockerImage } | + #SourceInline //c + +// these are sources which are suitable as a sub-source for +// SourceBuild's .source +#SubSourceVariant: { context: #SourceContext } | + { git: #SourceGit } | + { http: #SourceHTTP } | + { image: #SourceDockerImage } | + #SourceInline //c + +// base properties which are source-independent +#SourceBase: { path?: string //c + includes?: [string] //c + excludes?: [string] //c + } + +#Source: {#SourceBase, #SourceVariant} +#SubSource: {#SourceBase, #SubSourceVariant} + #PackageDependencies: close({ - build?: [string]: ([] | [string]) - runtime?: [string]: ([] | [string]) - recommends?: [string]: ([] | [string]) - test?: [string]: ([] | [string]) + build?: [string]: (null | [string]) //c + runtime?: [string]: (null | [string]) //c + recommends?: [string]: (null | [string]) //c + test?: [string]: (null | [string]) //c }) #ArtifactBuild: close({ - steps: [#BuildStep] - env?: [string]: string + steps: [#BuildStep] //c + env?: [string]: string //c }) #ArtifactConfig: close({ - name: string - subPath?: string + // TODO: restrict path? + subPath?: string //c + name?: string //c }) #Artifacts: close({ - binaries?: [string]: #ArtifactConfig | null - manpages?: [string]: #ArtifactConfig | null + binaries?: [string]: #ArtifactConfig //c + manpages?: [string]: #ArtifactConfig //c +}) + +// TEST: mixes of fields set +#CheckOutput: close({ + equals?: string //c + contains?: [string] //c + matches?: string //c + starts_with?: string //c + ends_with?: string //c + empty?: bool //c + }) + +#TestStep: close({ + command?: string //c + env?: [string]: string //c + stdout?: #CheckOutput //c + stderr?: #CheckOutput //c + stdin?: string //c +}) + +#FileCheckOutput: { + #CheckOutput //c + permissions?: perms //c + isDir?: bool //c + notExist?: bool //c +} + +// TEST: empty steps +#TestSpec: close({ + name: string //c + dir?: filepath //c + mounts?: [#SourceMount] //c + cacheDirs?: [string]: #CacheDirConfig //c + env?: [string]: string //c + steps: [#TestStep] //c + files?: [filepath]: #FileCheckOutput //c +}) + +// TEST: invalid path +#SymlinkTarget: close({ + path: string //c +}) + +#PostInstall: close({ + symlinks?: [string]: #SymlinkTarget //c }) #ImageConfig: close({ - entrypoint?: string - cmd?: string - env?: [string] - labels?: [string]: string - volumes?: [string]: null - working_dir?: string - stop_signal?: string - base?: string + entrypoint?: string //c + cmd?: string //c + env?: [string] //c + labels?: [string]: string //c + volumes?: [string]: null //c + working_dir?: string //c + stop_signal?: string //c + base?: string //c + post?: #PostInstall + user?: string //c }) -// TODO: export somehow? +#Frontend: close({ + image: string //c + cmdline?: string //c +}) + +#Target: close({ + dependencies?: #PackageDependencies //c + image?: #ImageConfig // c + frontend?: #Frontend // c + tests?: [#TestSpec] // c +}) + + +#PatchSpec: close({ + source: #sourceName //c + strip?: int //c +}) + +// test: valid and invalid times +#ChangelogEntry: close({ + date: time.Time + author: string + changes: [string] +}) + +// test: multiple/no sources specified #Spec: close({ - name: string - description: string - website: string - version: string - revision: uint - vendor: string - packager: string - license: string - - sources: [string]: #Source - - dependencies: #PackageDependencies - build: #ArtifactBuild - artifacts: #Artifacts - image: #ImageConfig + name: string //c + description: string //c + website?: string //c + version: string //c + revision: uint //c + noArch?: bool //c + + conflicts?: [string]: [string] //c + replaces?: [string]: [string] //c + provides?: [string] //c + + sources?: [#sourceName]: #Source //c + + patches?: [#sourceName]: [#PatchSpec] //c + + build?: #ArtifactBuild //c + + // should probably validate magic variables here + // TARGET* vars should not have any default values applied + args?: [string]: (string | null) //c + + license: string //c + + vendor: string //c + + packager: string //c + + artifacts?: #Artifacts //c + + targets?: [string]: #Target //c + + dependencies?: #PackageDependencies //c + + image?: #ImageConfig //c + + changelog?: [#ChangelogEntry] //c + + tests?: [#TestSpec] //c }) #Spec \ No newline at end of file From d902d2fb6d7b67e6f50c3d4de975b980bd24a9af Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 7 Mar 2024 01:39:46 +0000 Subject: [PATCH 3/8] Add beginnings of cue_validate_test.go --- cue/frontend.yml | 57 ----------------- cue_validate_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 17 ++++- 4 files changed, 162 insertions(+), 60 deletions(-) delete mode 100644 cue/frontend.yml create mode 100644 cue_validate_test.go diff --git a/cue/frontend.yml b/cue/frontend.yml deleted file mode 100644 index f2498aa25..000000000 --- a/cue/frontend.yml +++ /dev/null @@ -1,57 +0,0 @@ -# syntax=ghcr.io/azure/dalec/frontend:latest - -name: dalec-rpm-frontend -description: A test fixture which builds this project as an RPM, mainly to validate generating sources from a command. -website: https://www.github.com/Azure/dalec -version: 0.0.1 -revision: 1 -vendor: Microsoft - -packager: Microsoft -license: Apache 2.0 -sources: - src: - path: /blah - context: - name: . - gomodcache: - path: /build/gomodcache - image: - ref: mcr.microsoft.com/oss/go/microsoft/golang:1.21 - cmd: - dir: /build/src - steps: - - command: go mod download - env: - GOMODCACHE: /build/gomodcache - mounts: - - dest: /build/src - spec: - context: - name: . - -dependencies: - build: - golang: [] - -build: - steps: - - command: | - export GOMODCACHE="$(pwd)/gomodcache" - cd src - go build -o ../frontend ./cmd/frontend - - env: - GOPROXY: direct - CGO_ENABLED: "0" - GOGC: off - GOFLAGS: -trimpath - GOPATH: /go - GOROOT: /usr/lib/golang - -artifacts: - binaries: - frontend: - -image: - entrypoint: frontend diff --git a/cue_validate_test.go b/cue_validate_test.go new file mode 100644 index 000000000..9ef59da21 --- /dev/null +++ b/cue_validate_test.go @@ -0,0 +1,143 @@ +package dalec + +import ( + "fmt" + "os" + "testing" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/errors" + pkgyaml "cuelang.org/go/pkg/encoding/yaml" + "github.com/google/go-cmp/cmp" +) + +// assumes single cue file path +func withCueModule(t *testing.T, modPath string, runTest func(t *testing.T, cueSpec cue.Value)) { + var c *cue.Context = cuecontext.New() + contents, err := os.ReadFile(modPath) + if err != nil { + t.Fatal(err) + } + + val := c.CompileBytes(contents) + if val.Err() != nil { + t.Error(err) + } + + runTest(t, val) +} + +func sel(v cue.Value, fieldPath string) cue.Value { + return v.LookupPath(cue.ParsePath(fieldPath)) +} + +var testHeader = ` +name: "test-spec" +description: "Test Spec Header" +version: "0.1" +revision: 1 +license: "Apache" +vendor: "Microsoft" +packager: "Azure Container Upstream" +` + +func appendHeader(rest string) string { + return testHeader + "\n" + rest + +} + +func assertYamlValidate(t *testing.T, marshalDef string, yaml string, wantMsgs []string) { + withCueModule(t, "cue/spec.cue", func(t *testing.T, v cue.Value) { + against := sel(v, marshalDef) + fmt.Println(against) + _, err := pkgyaml.Validate([]byte(yaml), against) + + errs := errors.Errors(err) + msgs := []string{} + for _, e := range errs { + msgs = append(msgs, e.Error()) + } + + if !cmp.Equal(msgs, wantMsgs) { + t.Fatalf("Unexpected errors: %v\n", cmp.Diff(errs, wantMsgs)) + } + }) +} + +func TestCueValidate_CacheDirConfig(t *testing.T) { + tests := []struct { + name string + yaml string + marshalDef string + wantErrs []string + }{ + { + name: "invalid cache dir type", + yaml: `mode: "unknown-mode"`, + marshalDef: "#CacheDirConfig", + wantErrs: []string{ + "#CacheDirConfig.mode: 3 errors in empty disjunction:", + `#CacheDirConfig.mode: conflicting values "locked" and "unknown-mode"`, + `#CacheDirConfig.mode: conflicting values "private" and "unknown-mode"`, + `#CacheDirConfig.mode: conflicting values "shared" and "unknown-mode"`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.marshalDef, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_Sources(t *testing.T) { + tests := []struct { + name string + yaml string + marshalDef string + wantErrs []string + }{ + { + name: "invalid source name character", + yaml: appendHeader( + `sources: + mysrc$: + git: + url: "https://github.com/some-repo.git"`), + marshalDef: "#Spec", + wantErrs: []string{"#Spec.sources.mysrc$: field not allowed"}, + }, + { + name: "multiple source types defined", + yaml: ` +git: + url: "https://github.com/some-repo.git" +http: + url: "https://some-site/get-source"`, + marshalDef: "#Source", + wantErrs: []string{ + "#Source: 2 errors in empty disjunction:", + "#Source.git: field not allowed", + "#Source.http: field not allowed", + }, + }, + { + name: "source: key provided, no sources defined", + marshalDef: "#Spec", + yaml: appendHeader( + `sources: +`), + wantErrs: []string{ + "#Spec.sources: conflicting values null and {[#sourceName]:#Source} (mismatched types null and struct)", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.marshalDef, tt.yaml, tt.wantErrs) + }) + } +} diff --git a/go.mod b/go.mod index 08404b4ac..c2a5070ea 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Azure/dalec go 1.20 require ( + cuelang.org/go v0.7.1 github.com/containerd/containerd v1.7.2 github.com/cpuguy83/dockercfg v0.3.1 github.com/cpuguy83/go-docker v0.3.0 @@ -30,6 +31,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/console v1.0.3 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/containerd/ttrpc v1.2.2 // indirect @@ -46,11 +48,11 @@ require ( github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect github.com/in-toto/in-toto-golang v0.5.0 // indirect github.com/klauspost/compress v1.17.2 // indirect - github.com/kr/pretty v0.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect @@ -58,6 +60,7 @@ require ( github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/stretchr/testify v1.8.4 // indirect diff --git a/go.sum b/go.sum index c10bb1c05..64c30170c 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 h1:zkiIe8AxZ/kDjqQN+mDKc5BxoVJOqioSdqApjc+eB1I= +cuelang.org/go v0.7.1 h1:wSuUSIKR9M1yrph57l8EJATWVRWHaq/Zd0dFUL10PC8= +cuelang.org/go v0.7.1/go.mod h1:ix+3dM/bSpdG9xg6qpCgnJnpeLtciZu+O/rDbywoMII= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= @@ -70,6 +73,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= @@ -106,6 +111,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -131,6 +137,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= @@ -200,6 +207,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -225,12 +233,12 @@ github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -238,6 +246,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/moby/buildkit v0.12.5 h1:RNHH1l3HDhYyZafr5EgstEu8aGNCwyfvMtrQDtjH9T0= github.com/moby/buildkit v0.12.5/go.mod h1:YGwjA2loqyiYfZeEo8FtI7z4x5XponAaIWsWcSjWwso= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -250,6 +259,8 @@ github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= +github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -267,8 +278,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c h1:fPpdjePK1atuOg28PXfNSqgwf9I/qD1Hlo39JFwKBXk= github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE= github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= From 4b3491d3ff09d44d9670ebcd01cedb436d065961 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 7 Mar 2024 23:41:06 +0000 Subject: [PATCH 4/8] Update spec.cue, add cue_validate unit tests, begin fixing test fixtures --- cue/spec.cue | 253 +++++++++++++++----------------- cue_validate_test.go | 341 ++++++++++++++++++++++++++++++++++++++++--- load.go | 30 ++++ 3 files changed, 469 insertions(+), 155 deletions(-) diff --git a/cue/spec.cue b/cue/spec.cue index 84c9cb182..eb3290127 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -12,101 +12,96 @@ import ( // Test harness should verify that cue spec catches errors for subfields // particularly for things people are likely to make errors with -// allow any filepath with no whitespace characters. can further restrict later -let filepath = =~ "^[^\\0\\s]+$" +// this alias exists for the sake of being explicit, and in case we want to add restrictions later +// on what kind of filepaths we allow in Dalec +let filepath = string // {0, ..., 511} = {0o000, ..., 0o777} are valid unix perms let perms = >= 0 & <= 0o777 // a source name must be alphanumeric, with the inclusion of '_' and '-' // TODO: consider including '.' to allow sources with a file extension -// TEST: invalid source names -#sourceName: =~ "^[a-zA-Z0-9_-]+$" +let sourceName = =~ "^[a-zA-Z0-9_-.]+$" #BuildStep: close({ - command: string //c - env?: [string]: string //c + command: string + env?: [string]: string }) -// TEST: invalid cache keys #CacheDirConfig: close({ - mode?: "shared" | "private" | "locked" //c - key?: string //c - include_distro_key?: bool //c - include_arch_key?: bool //c + mode?: "shared" | "private" | "locked" + key?: string + include_distro_key?: bool + include_arch_key?: bool }) #Command: close({ - dir?: filepath //c - mounts?: [#SourceMount] //c - cache_dirs?: [filepath]: #CacheDirConfig //c - env?: [string]: string //c - steps: [#BuildStep] //c + dir?: filepath + mounts?: [...#SourceMount] + cache_dirs?: [filepath]: #CacheDirConfig + env?: [string]: string + steps: [...#BuildStep] }) -// TEST: structural cycle case -#SourceMount: close({ - dest: filepath //c +#SourceMount: { + dest: filepath // structural cycle formed by source.image.mounts.spec.source must terminate somehow for cue to accept - // TODO: look into how this cycle could be terminated further up the chain, as this currently would allow - // a source mount with no provided spec which is invalid (though a significant edge case) - spec: null | #Source //c -}) + // even though there are non-recursive sources, (i.e., it is not required that SourceMount contain a recursive source, so there is + // implicitly a base case), cue's cycle detection is not currently sufficient to detect this. + spec: null | #Source +} #SourceContext: close({ name?: string -}) //c +}) -#SourceDockerImage: close({ //c - ref: string //c - cmd?: #Command //c +#SourceDockerImage: close({ + ref: string + cmd?: #Command }) #SourceHTTP: close({ // TODO: specify url field further? - url: string //c + url: string }) #SourceBuild: close({ - source?: #SubSource //c - dockerfile_path?: string //c - target?: string //c - args?: [string]: string //c + source?: #SubSource + dockerfile_path?: filepath + target?: string + args?: [string]: string }) -#SourceGit: close({ +#SourceGit: { // TODO: specify URL field further? - url: string //c - commit?: string //c - keepGitDir?: bool //c -}) + url: string + commit?: string + keepGitDir?: bool +} -// TEST UID/GID < 0 #SourceInlineFile: close({ - contents?: string //c - permissions?: perms //c - uid?: >= 0 //c - gid?: >= 0 //c + contents?: string + permissions?: perms + uid?: >= 0 + gid?: >= 0 }) - -// TEST UID/GID < 0 #SourceInlineDir: close({ - files?: [string]: #SourceInlineFile //c - permissions?: perms //c - uid?: >= 0 //c - gid?: >= 0 //c + files?: [sourceName]: #SourceInlineFile + permissions?: perms + uid?: >= 0 + gid?: >= 0 }) #SourceInline: { file: #SourceInlineFile } | - { dir: #SourceInlineDir } //c + { dir: #SourceInlineDir } #SourceVariant: { context: #SourceContext } | { git: #SourceGit } | { build: #SourceBuild } | { http: #SourceHTTP } | { image: #SourceDockerImage } | - #SourceInline //c + #SourceInline // these are sources which are suitable as a sub-source for // SourceBuild's .source @@ -114,163 +109,157 @@ let perms = >= 0 & <= 0o777 { git: #SourceGit } | { http: #SourceHTTP } | { image: #SourceDockerImage } | - #SourceInline //c + #SourceInline // base properties which are source-independent -#SourceBase: { path?: string //c - includes?: [string] //c - excludes?: [string] //c +#SourceBase: { path?: filepath + includes?: [...string] + excludes?: [...string] } #Source: {#SourceBase, #SourceVariant} #SubSource: {#SourceBase, #SubSourceVariant} #PackageDependencies: close({ - build?: [string]: (null | [string]) //c - runtime?: [string]: (null | [string]) //c - recommends?: [string]: (null | [string]) //c - test?: [string]: (null | [string]) //c + build?: [string]: (null | [...string]) + runtime?: [string]: (null | [...string]) + recommends?: [string]: (null | [...string]) + test?: [string]: (null | [...string]) }) #ArtifactBuild: close({ - steps: [#BuildStep] //c - env?: [string]: string //c + steps: [...#BuildStep] + env?: [string]: string }) #ArtifactConfig: close({ - // TODO: restrict path? - subPath?: string //c - name?: string //c + sub_path?: filepath + name?: string }) #Artifacts: close({ - binaries?: [string]: #ArtifactConfig //c - manpages?: [string]: #ArtifactConfig //c + binaries?: [filepath]: #ArtifactConfig + manpages?: [filepath]: #ArtifactConfig }) -// TEST: mixes of fields set #CheckOutput: close({ - equals?: string //c - contains?: [string] //c - matches?: string //c - starts_with?: string //c - ends_with?: string //c - empty?: bool //c + equals?: string + contains?: [...string] + matches?: string + starts_with?: string + ends_with?: string + empty?: bool }) #TestStep: close({ - command?: string //c - env?: [string]: string //c - stdout?: #CheckOutput //c - stderr?: #CheckOutput //c - stdin?: string //c + command?: string + env?: [string]: string + stdout?: #CheckOutput + stderr?: #CheckOutput + stdin?: string }) #FileCheckOutput: { - #CheckOutput //c - permissions?: perms //c - isDir?: bool //c - notExist?: bool //c + #CheckOutput + permissions?: perms + isDir?: bool + notExist?: bool } -// TEST: empty steps #TestSpec: close({ - name: string //c - dir?: filepath //c - mounts?: [#SourceMount] //c - cacheDirs?: [string]: #CacheDirConfig //c - env?: [string]: string //c - steps: [#TestStep] //c - files?: [filepath]: #FileCheckOutput //c + name: string + dir?: filepath + mounts?: [...#SourceMount] + cacheDirs?: [string]: #CacheDirConfig + env?: [string]: string + steps: [...#TestStep] + files?: [filepath]: #FileCheckOutput }) -// TEST: invalid path #SymlinkTarget: close({ - path: string //c + path: filepath }) #PostInstall: close({ - symlinks?: [string]: #SymlinkTarget //c + symlinks?: [filepath]: #SymlinkTarget }) #ImageConfig: close({ - entrypoint?: string //c - cmd?: string //c - env?: [string] //c - labels?: [string]: string //c - volumes?: [string]: null //c - working_dir?: string //c - stop_signal?: string //c - base?: string //c + entrypoint?: string + cmd?: string + env?: [...string] + labels?: [string]: string + volumes?: [string]: null + working_dir?: string + stop_signal?: string + base?: string post?: #PostInstall - user?: string //c + user?: string }) #Frontend: close({ - image: string //c - cmdline?: string //c + image: string + cmdline?: string }) #Target: close({ - dependencies?: #PackageDependencies //c + dependencies?: #PackageDependencies image?: #ImageConfig // c frontend?: #Frontend // c - tests?: [#TestSpec] // c + tests?: [...#TestSpec] // c }) - #PatchSpec: close({ - source: #sourceName //c - strip?: int //c + source: sourceName + strip?: int }) // test: valid and invalid times #ChangelogEntry: close({ date: time.Time author: string - changes: [string] + changes: [...string] }) -// test: multiple/no sources specified #Spec: close({ - name: string //c - description: string //c - website?: string //c - version: string //c - revision: uint //c - noArch?: bool //c + name: string + description: string + website?: string + version: string + revision: >= 0 + noArch?: bool - conflicts?: [string]: [string] //c - replaces?: [string]: [string] //c - provides?: [string] //c + conflicts?: [string]: [string] + replaces?: [string]: [string] + provides?: [...string] - sources?: [#sourceName]: #Source //c + sources?: [sourceName]: #Source - patches?: [#sourceName]: [#PatchSpec] //c + patches?: [sourceName]: [...#PatchSpec] - build?: #ArtifactBuild //c + build?: #ArtifactBuild // should probably validate magic variables here // TARGET* vars should not have any default values applied - args?: [string]: (string | null) //c + args?: [string]: (string | null) - license: string //c + license: string - vendor: string //c + vendor: string - packager: string //c + packager: string - artifacts?: #Artifacts //c + artifacts?: #Artifacts - targets?: [string]: #Target //c + targets?: [string]: #Target - dependencies?: #PackageDependencies //c + dependencies?: #PackageDependencies - image?: #ImageConfig //c + image?: #ImageConfig - changelog?: [#ChangelogEntry] //c + changelog?: [...#ChangelogEntry] - tests?: [#TestSpec] //c + tests?: [...#TestSpec] }) #Spec \ No newline at end of file diff --git a/cue_validate_test.go b/cue_validate_test.go index 9ef59da21..c1c53cd52 100644 --- a/cue_validate_test.go +++ b/cue_validate_test.go @@ -44,12 +44,11 @@ packager: "Azure Container Upstream" func appendHeader(rest string) string { return testHeader + "\n" + rest - } -func assertYamlValidate(t *testing.T, marshalDef string, yaml string, wantMsgs []string) { +func assertYamlValidate(t *testing.T, unmarshalInto string, yaml string, wantMsgs []string) { withCueModule(t, "cue/spec.cue", func(t *testing.T, v cue.Value) { - against := sel(v, marshalDef) + against := sel(v, unmarshalInto) fmt.Println(against) _, err := pkgyaml.Validate([]byte(yaml), against) @@ -67,15 +66,15 @@ func assertYamlValidate(t *testing.T, marshalDef string, yaml string, wantMsgs [ func TestCueValidate_CacheDirConfig(t *testing.T) { tests := []struct { - name string - yaml string - marshalDef string - wantErrs []string + name string + yaml string + unmarshalInto string + wantErrs []string }{ { - name: "invalid cache dir type", - yaml: `mode: "unknown-mode"`, - marshalDef: "#CacheDirConfig", + name: "invalid cache dir type", + yaml: `mode: "unknown-mode"`, + unmarshalInto: "#CacheDirConfig", wantErrs: []string{ "#CacheDirConfig.mode: 3 errors in empty disjunction:", `#CacheDirConfig.mode: conflicting values "locked" and "unknown-mode"`, @@ -87,27 +86,288 @@ func TestCueValidate_CacheDirConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assertYamlValidate(t, tt.marshalDef, tt.yaml, tt.wantErrs) + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_SourceInlineFile(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "uid < 0", + yaml: `uid: -1`, + unmarshalInto: "#SourceInlineFile", + wantErrs: []string{ + "#SourceInlineFile.uid: invalid value -1 (out of bound >=0)", + }, + }, + { + name: "gid < 0", + yaml: `gid: -1`, + unmarshalInto: "#SourceInlineFile", + wantErrs: []string{ + "#SourceInlineFile.gid: invalid value -1 (out of bound >=0)", + }, + }, + { + name: "invalid permissions", + yaml: `permissions: 999`, + unmarshalInto: "#SourceInlineFile", + wantErrs: []string{ + "#SourceInlineFile.permissions: invalid value 999 (out of bound <=511)", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_SourceInlineDir(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "uid < 0", + yaml: `uid: -1`, + unmarshalInto: "#SourceInlineDir", + wantErrs: []string{ + "#SourceInlineDir.uid: invalid value -1 (out of bound >=0)", + }, + }, + { + name: "gid < 0", + yaml: `gid: -1`, + unmarshalInto: "#SourceInlineDir", + wantErrs: []string{ + "#SourceInlineDir.gid: invalid value -1 (out of bound >=0)", + }, + }, + { + name: "invalid permissions", + yaml: `permissions: 999`, + unmarshalInto: "#SourceInlineDir", + wantErrs: []string{ + "#SourceInlineDir.permissions: invalid value 999 (out of bound <=511)", + }, + }, + { + name: "invalid nested file name", + yaml: ` +files: + my/file: + contents: "some file contents"`, + unmarshalInto: "#SourceInlineDir", + wantErrs: []string{ + `#SourceInlineDir.files."my/file": field not allowed`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_SourceInline(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "file and dir variants both defined", + unmarshalInto: "#SourceInline", + yaml: ` +file: + contents: "some file contents" +dir: + files: + file1: + contents: "some file contents" +`, + wantErrs: []string{ + "#SourceInline: 2 errors in empty disjunction:", + "#SourceInline.dir: field not allowed", + "#SourceInline.file: field not allowed", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_SourceBuild(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "recursive build", + unmarshalInto: "#SourceBuild", + yaml: ` +source: + build: + source: + inline: + file: + contents: "FROM scratch"`, + wantErrs: []string{ + "#SourceBuild.source.build: field not allowed", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_SourceMount(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "nested source mount", + unmarshalInto: "#SourceMount", + yaml: ` +dest: "/app/sub1" +spec: + image: + ref: "nested-docker-image" + cmd: + dir: "/app/sub2" + mounts: + - dest: "/app/sub2/sub3" + spec: + image: + ref: "nested-docker-image" + cmd: + mounts: + - dest: "/app/sub2/sub3/sub4" + spec: + http: + url: "http://myhost.file.txt" + steps: + - command: "echo hello nested world" + steps: + - command: "echo hello nested world" +`, + wantErrs: []string{}, + }, + { + name: "BUG: null spec in source mount", + unmarshalInto: "#SourceMount", + yaml: ` +dest: "/app/sub1" +spec: +`, + wantErrs: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_FileCheckOutput(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "embedded check output", + unmarshalInto: "#FileCheckOutput", + yaml: ` +contains: + - "abcd" + - "efgh" +starts_with: "abcd" +`, + wantErrs: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_PatchSpec(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "invalid source name", + unmarshalInto: "#PatchSpec", + yaml: ` +source: mysource* +strip: 1 +`, + wantErrs: []string{ + `#PatchSpec.source: invalid value "mysource*" (out of bound =~"^[a-zA-Z0-9_-]+$")`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) }) } } func TestCueValidate_Sources(t *testing.T) { tests := []struct { - name string - yaml string - marshalDef string - wantErrs []string + name string + yaml string + unmarshalInto string + wantErrs []string }{ { - name: "invalid source name character", + name: "invalid source name character in sources", yaml: appendHeader( `sources: mysrc$: git: url: "https://github.com/some-repo.git"`), - marshalDef: "#Spec", - wantErrs: []string{"#Spec.sources.mysrc$: field not allowed"}, + unmarshalInto: "#Spec", + wantErrs: []string{"#Spec.sources.mysrc$: field not allowed"}, }, { name: "multiple source types defined", @@ -116,7 +376,7 @@ git: url: "https://github.com/some-repo.git" http: url: "https://some-site/get-source"`, - marshalDef: "#Source", + unmarshalInto: "#Source", wantErrs: []string{ "#Source: 2 errors in empty disjunction:", "#Source.git: field not allowed", @@ -124,20 +384,55 @@ http: }, }, { - name: "source: key provided, no sources defined", - marshalDef: "#Spec", + name: "source: key provided, no sources defined", + unmarshalInto: "#Spec", yaml: appendHeader( `sources: `), wantErrs: []string{ - "#Spec.sources: conflicting values null and {[#sourceName]:#Source} (mismatched types null and struct)", + "#Spec.sources: conflicting values null and {[sourceName]:#Source} (mismatched types null and struct)", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assertYamlValidate(t, tt.marshalDef, tt.yaml, tt.wantErrs) + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) + }) + } +} + +func TestCueValidate_ChangelogEntry(t *testing.T) { + tests := []struct { + name string + yaml string + unmarshalInto string + wantErrs []string + }{ + { + name: "invalid date", + yaml: ` +date: invalid-date +author: "John Doe"`, + unmarshalInto: "#ChangelogEntry", + wantErrs: []string{ + `#ChangelogEntry.date: invalid value "invalid-date" (does not satisfy time.Time): error in call to time.Time: invalid time "invalid-date"`, + }, + }, + { + name: "valid date", + yaml: ` +date: "2021-01-01T00:00:00Z" +author: "First Last" +`, + unmarshalInto: "#ChangelogEntry", + wantErrs: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertYamlValidate(t, tt.unmarshalInto, tt.yaml, tt.wantErrs) }) } } diff --git a/load.go b/load.go index 68c972ca4..859bf8630 100644 --- a/load.go +++ b/load.go @@ -1,18 +1,25 @@ package dalec import ( + _ "embed" goerrors "errors" "fmt" "os" "path" "strings" + "cuelang.org/go/cue/cuecontext" + cueerrors "cuelang.org/go/cue/errors" + pkgyaml "cuelang.org/go/pkg/encoding/yaml" "github.com/goccy/go-yaml" "github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/moby/buildkit/frontend/dockerui" "github.com/pkg/errors" ) +//go:embed cue/spec.cue +var cueSpec string + func knownArg(key string) bool { switch key { case "BUILDKIT_SYNTAX": @@ -259,6 +266,25 @@ func (s *Spec) SubstituteArgs(env map[string]string) error { return nil } +func CueValidate(dt []byte) error { + cueCtx := cuecontext.New() + v := cueCtx.CompileString(cueSpec) + + if v.Err() != nil { + return v.Err() + } + + _, err := pkgyaml.Validate(dt, v) + errs := (cueerrors.Errors(err)) + + var retErrs []error = make([]error, 0, len(errs)) + for _, err := range errs { + retErrs = append(retErrs, err) + } + + return goerrors.Join(retErrs...) +} + // LoadSpec loads a spec from the given data. func LoadSpec(dt []byte) (*Spec, error) { var spec Spec @@ -268,6 +294,10 @@ func LoadSpec(dt []byte) (*Spec, error) { return nil, fmt.Errorf("error stripping x-fields: %w", err) } + if err := CueValidate(dt); err != nil { + return nil, fmt.Errorf("error validating spec with cue: %w", err) + } + if err := yaml.UnmarshalWithOptions(dt, &spec, yaml.Strict()); err != nil { return nil, fmt.Errorf("error unmarshalling spec: %w", err) } From 6c66a761ad3a50f2b51d699dcc08fc3a657aa486 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 8 Mar 2024 20:15:56 +0000 Subject: [PATCH 5/8] Fix cue spec, cue_validate_test, and a few test fixtures --- cue/spec.cue | 62 ++++++++++------------ cue_validate_test.go | 21 ++++---- test/fixtures/local-context.yml | 2 +- test/fixtures/moby-runc.yml | 2 +- test/fixtures/unmarshall/source-inline.yml | 9 ++++ 5 files changed, 49 insertions(+), 47 deletions(-) diff --git a/cue/spec.cue b/cue/spec.cue index eb3290127..89855d80f 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -4,27 +4,19 @@ import ( "time" ) -// further specify string fields that deal with urls? -// move under dalec/ -// slot in and run against all existing test specs - -// Create test harness -// Test harness should verify that cue spec catches errors for subfields -// particularly for things people are likely to make errors with - -// this alias exists for the sake of being explicit, and in case we want to add restrictions later +// This alias exists for the sake of being explicit, and in case we want to add restrictions later // on what kind of filepaths we allow in Dalec let filepath = string // {0, ..., 511} = {0o000, ..., 0o777} are valid unix perms let perms = >= 0 & <= 0o777 -// a source name must be alphanumeric, with the inclusion of '_' and '-' -// TODO: consider including '.' to allow sources with a file extension -let sourceName = =~ "^[a-zA-Z0-9_-.]+$" +// A source name must be alphanumeric, with the inclusion of '_', '-', and '.' +let sourceName = =~ "^[a-zA-Z0-9_-|.]+$" +let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" #BuildStep: close({ - command: string + command: string env?: [string]: string }) @@ -93,15 +85,16 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" gid?: >= 0 }) -#SourceInline: { file: #SourceInlineFile } | - { dir: #SourceInlineDir } +#SourceInlineVariant: { file: #SourceInlineFile } | + { dir: #SourceInlineDir } +#SourceInline: { inline: #SourceInlineVariant } #SourceVariant: { context: #SourceContext } | { git: #SourceGit } | { build: #SourceBuild } | { http: #SourceHTTP } | { image: #SourceDockerImage } | - #SourceInline + { inline: #SourceInlineVariant } // these are sources which are suitable as a sub-source for // SourceBuild's .source @@ -133,13 +126,13 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" }) #ArtifactConfig: close({ - sub_path?: filepath + subpath?: filepath name?: string }) #Artifacts: close({ - binaries?: [filepath]: #ArtifactConfig - manpages?: [filepath]: #ArtifactConfig + binaries?: [filepath]: (null | #ArtifactConfig) + manpages?: [filepath]: (null | #ArtifactConfig) }) #CheckOutput: close({ @@ -162,8 +155,8 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" #FileCheckOutput: { #CheckOutput permissions?: perms - isDir?: bool - notExist?: bool + is_dir?: bool + not_exist?: bool } #TestSpec: close({ @@ -173,7 +166,7 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" cacheDirs?: [string]: #CacheDirConfig env?: [string]: string steps: [...#TestStep] - files?: [filepath]: #FileCheckOutput + files?: [filepath]: (null | #FileCheckOutput) }) #SymlinkTarget: close({ @@ -203,7 +196,7 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" }) #Target: close({ - dependencies?: #PackageDependencies + dependencies?: (null | #PackageDependencies) image?: #ImageConfig // c frontend?: #Frontend // c tests?: [...#TestSpec] // c @@ -214,7 +207,6 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" strip?: int }) -// test: valid and invalid times #ChangelogEntry: close({ date: time.Time author: string @@ -222,15 +214,15 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" }) #Spec: close({ - name: string - description: string + name: string | *"My Package" + description: string | *"My Dalec Package" website?: string - version: string - revision: >= 0 - noArch?: bool + version: string | *"0.1" + revision: (buildVar | uint) | *1 + noarch?: bool - conflicts?: [string]: [string] - replaces?: [string]: [string] + conflicts?: [string]: (null | [...string]) + replaces?: [string]: (null | [...string]) provides?: [...string] sources?: [sourceName]: #Source @@ -241,13 +233,13 @@ let sourceName = =~ "^[a-zA-Z0-9_-.]+$" // should probably validate magic variables here // TARGET* vars should not have any default values applied - args?: [string]: (string | null) + args?: [string]: (string | int | null) - license: string + license: string | *"Needs License" - vendor: string + vendor: string | *"My Vendor" - packager: string + packager: string | *"Azure Container Upstream" artifacts?: #Artifacts diff --git a/cue_validate_test.go b/cue_validate_test.go index c1c53cd52..147700b33 100644 --- a/cue_validate_test.go +++ b/cue_validate_test.go @@ -193,17 +193,18 @@ func TestCueValidate_SourceInline(t *testing.T) { name: "file and dir variants both defined", unmarshalInto: "#SourceInline", yaml: ` -file: - contents: "some file contents" -dir: - files: - file1: - contents: "some file contents" +inline: + file: + contents: "some file contents" + dir: + files: + file1: + contents: "some file contents" `, wantErrs: []string{ - "#SourceInline: 2 errors in empty disjunction:", - "#SourceInline.dir: field not allowed", - "#SourceInline.file: field not allowed", + "#SourceInline.inline: 2 errors in empty disjunction:", + "#SourceInline.inline.dir: field not allowed", + "#SourceInline.inline.file: field not allowed", }, }, } @@ -340,7 +341,7 @@ source: mysource* strip: 1 `, wantErrs: []string{ - `#PatchSpec.source: invalid value "mysource*" (out of bound =~"^[a-zA-Z0-9_-]+$")`, + `#PatchSpec.source: invalid value "mysource*" (out of bound =~"^[a-zA-Z0-9_-|.]+$")`, }, }, } diff --git a/test/fixtures/local-context.yml b/test/fixtures/local-context.yml index 527ba20ef..9af7240f0 100644 --- a/test/fixtures/local-context.yml +++ b/test/fixtures/local-context.yml @@ -60,7 +60,7 @@ build: changelog: - - date: 2023-10-10 + date: 2023-10-10T00:00:00Z author: Brian Goff changes: - Added a changelog item diff --git a/test/fixtures/moby-runc.yml b/test/fixtures/moby-runc.yml index 0b68f6cd0..e6593daa6 100644 --- a/test/fixtures/moby-runc.yml +++ b/test/fixtures/moby-runc.yml @@ -67,7 +67,7 @@ sources: build: env: - CGO_ENABLED: 1 + CGO_ENABLED: "1" GOGC: off GOFLAGS: -trimpath steps: diff --git a/test/fixtures/unmarshall/source-inline.yml b/test/fixtures/unmarshall/source-inline.yml index 0dfee532a..d31a19cff 100644 --- a/test/fixtures/unmarshall/source-inline.yml +++ b/test/fixtures/unmarshall/source-inline.yml @@ -1,3 +1,12 @@ +# # name: "name" +# description: "desc" +# revision: 1 +# version: "1.0" +# license: "n/a" +# vendor: "Microsoft" +# packager: "n/a" + + sources: TestFileOctelPreGo113: inline: From ce2eac2d11fc021cb1b8eb23c42326b335d6425d Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 8 Mar 2024 20:23:49 +0000 Subject: [PATCH 6/8] Update some comments and remove close(...) where it is already implicit --- cue/spec.cue | 112 +++++++++++---------- test/fixtures/unmarshall/source-inline.yml | 9 -- 2 files changed, 57 insertions(+), 64 deletions(-) diff --git a/cue/spec.cue b/cue/spec.cue index 89855d80f..7702ed05e 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -4,8 +4,10 @@ import ( "time" ) +// TODO: consider adding defaults to more fields + // This alias exists for the sake of being explicit, and in case we want to add restrictions later -// on what kind of filepaths we allow in Dalec +// for what kind of filepaths we allow in Dalec let filepath = string // {0, ..., 511} = {0o000, ..., 0o777} are valid unix perms @@ -15,54 +17,54 @@ let perms = >= 0 & <= 0o777 let sourceName = =~ "^[a-zA-Z0-9_-|.]+$" let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" -#BuildStep: close({ +#BuildStep: { command: string env?: [string]: string -}) +} -#CacheDirConfig: close({ +#CacheDirConfig: { mode?: "shared" | "private" | "locked" key?: string include_distro_key?: bool include_arch_key?: bool -}) +} -#Command: close({ +#Command: { dir?: filepath mounts?: [...#SourceMount] cache_dirs?: [filepath]: #CacheDirConfig env?: [string]: string steps: [...#BuildStep] -}) +} #SourceMount: { dest: filepath // structural cycle formed by source.image.mounts.spec.source must terminate somehow for cue to accept - // even though there are non-recursive sources, (i.e., it is not required that SourceMount contain a recursive source, so there is - // implicitly a base case), cue's cycle detection is not currently sufficient to detect this. + // even though there are non-recursive source variants so there is implicitly a base case, + // cue's cycle detection is not currently buggy in this case spec: null | #Source } -#SourceContext: close({ +#SourceContext: { name?: string -}) +} -#SourceDockerImage: close({ +#SourceDockerImage: { ref: string cmd?: #Command -}) +} -#SourceHTTP: close({ +#SourceHTTP: { // TODO: specify url field further? url: string -}) +} -#SourceBuild: close({ +#SourceBuild: { source?: #SubSource dockerfile_path?: filepath target?: string args?: [string]: string -}) +} #SourceGit: { // TODO: specify URL field further? @@ -71,19 +73,19 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" keepGitDir?: bool } -#SourceInlineFile: close({ +#SourceInlineFile: { contents?: string permissions?: perms uid?: >= 0 gid?: >= 0 -}) +} -#SourceInlineDir: close({ +#SourceInlineDir: { files?: [sourceName]: #SourceInlineFile permissions?: perms uid?: >= 0 gid?: >= 0 -}) +} #SourceInlineVariant: { file: #SourceInlineFile } | { dir: #SourceInlineDir } @@ -113,44 +115,44 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" #Source: {#SourceBase, #SourceVariant} #SubSource: {#SourceBase, #SubSourceVariant} -#PackageDependencies: close({ +#PackageDependencies: { build?: [string]: (null | [...string]) runtime?: [string]: (null | [...string]) recommends?: [string]: (null | [...string]) test?: [string]: (null | [...string]) -}) +} -#ArtifactBuild: close({ +#ArtifactBuild: { steps: [...#BuildStep] env?: [string]: string -}) +} -#ArtifactConfig: close({ +#ArtifactConfig: { subpath?: filepath name?: string -}) +} -#Artifacts: close({ +#Artifacts: { binaries?: [filepath]: (null | #ArtifactConfig) manpages?: [filepath]: (null | #ArtifactConfig) -}) +} -#CheckOutput: close({ +#CheckOutput: { equals?: string contains?: [...string] matches?: string starts_with?: string ends_with?: string empty?: bool - }) + } -#TestStep: close({ +#TestStep: { command?: string env?: [string]: string stdout?: #CheckOutput stderr?: #CheckOutput stdin?: string -}) +} #FileCheckOutput: { #CheckOutput @@ -159,7 +161,7 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" not_exist?: bool } -#TestSpec: close({ +#TestSpec: { name: string dir?: filepath mounts?: [...#SourceMount] @@ -167,17 +169,17 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" env?: [string]: string steps: [...#TestStep] files?: [filepath]: (null | #FileCheckOutput) -}) +} -#SymlinkTarget: close({ +#SymlinkTarget: { path: filepath -}) +} -#PostInstall: close({ +#PostInstall: { symlinks?: [filepath]: #SymlinkTarget -}) +} -#ImageConfig: close({ +#ImageConfig: { entrypoint?: string cmd?: string env?: [...string] @@ -188,32 +190,32 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" base?: string post?: #PostInstall user?: string -}) +} -#Frontend: close({ +#Frontend: { image: string cmdline?: string -}) +} -#Target: close({ +#Target: { dependencies?: (null | #PackageDependencies) - image?: #ImageConfig // c - frontend?: #Frontend // c - tests?: [...#TestSpec] // c -}) + image?: #ImageConfig + frontend?: #Frontend + tests?: [...#TestSpec] +} -#PatchSpec: close({ +#PatchSpec: { source: sourceName strip?: int -}) +} -#ChangelogEntry: close({ +#ChangelogEntry: { date: time.Time author: string changes: [...string] -}) +} -#Spec: close({ +#Spec: { name: string | *"My Package" description: string | *"My Dalec Package" website?: string @@ -231,7 +233,7 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" build?: #ArtifactBuild - // should probably validate magic variables here + // TODO: could probably validate magic variables here, // TARGET* vars should not have any default values applied args?: [string]: (string | int | null) @@ -252,6 +254,6 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" changelog?: [...#ChangelogEntry] tests?: [...#TestSpec] -}) +} #Spec \ No newline at end of file diff --git a/test/fixtures/unmarshall/source-inline.yml b/test/fixtures/unmarshall/source-inline.yml index d31a19cff..0dfee532a 100644 --- a/test/fixtures/unmarshall/source-inline.yml +++ b/test/fixtures/unmarshall/source-inline.yml @@ -1,12 +1,3 @@ -# # name: "name" -# description: "desc" -# revision: 1 -# version: "1.0" -# license: "n/a" -# vendor: "Microsoft" -# packager: "n/a" - - sources: TestFileOctelPreGo113: inline: From a29a2e47d7eb338a0b0e3b66bc611b29e11fc1d0 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 8 Mar 2024 22:09:47 +0000 Subject: [PATCH 7/8] Fix source name regex --- cue/spec.cue | 5 ++--- cue_validate_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cue/spec.cue b/cue/spec.cue index 7702ed05e..f12a7563e 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -14,8 +14,7 @@ let filepath = string let perms = >= 0 & <= 0o777 // A source name must be alphanumeric, with the inclusion of '_', '-', and '.' -let sourceName = =~ "^[a-zA-Z0-9_-|.]+$" -let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" +let sourceName = =~ "^[a-zA-Z0-9-._]+$" #BuildStep: { command: string @@ -220,7 +219,7 @@ let buildVar = =~ "^\\${[a-zA-Z_][a-zA-Z0-9_]*}$" description: string | *"My Dalec Package" website?: string version: string | *"0.1" - revision: (buildVar | uint) | *1 + revision: string | *"1" noarch?: bool conflicts?: [string]: (null | [...string]) diff --git a/cue_validate_test.go b/cue_validate_test.go index 147700b33..57deb7fbe 100644 --- a/cue_validate_test.go +++ b/cue_validate_test.go @@ -36,7 +36,7 @@ var testHeader = ` name: "test-spec" description: "Test Spec Header" version: "0.1" -revision: 1 +revision: "1" license: "Apache" vendor: "Microsoft" packager: "Azure Container Upstream" @@ -341,7 +341,7 @@ source: mysource* strip: 1 `, wantErrs: []string{ - `#PatchSpec.source: invalid value "mysource*" (out of bound =~"^[a-zA-Z0-9_-|.]+$")`, + `#PatchSpec.source: invalid value "mysource*" (out of bound =~"^[a-zA-Z0-9-._]+$")`, }, }, } From c3deb6c143c4fae44060fbc812e8c9e2e5a80ae1 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Sat, 9 Mar 2024 02:11:11 +0000 Subject: [PATCH 8/8] Add back uint as valid type for 'revision' in spec.cue --- cue/spec.cue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cue/spec.cue b/cue/spec.cue index f12a7563e..d2f9b9c5d 100644 --- a/cue/spec.cue +++ b/cue/spec.cue @@ -219,7 +219,7 @@ let sourceName = =~ "^[a-zA-Z0-9-._]+$" description: string | *"My Dalec Package" website?: string version: string | *"0.1" - revision: string | *"1" + revision: uint | string | *"1" noarch?: bool conflicts?: [string]: (null | [...string])