Add cue tooling & codegen to makefile

This commit is contained in:
Denis Arh
2022-01-26 16:27:36 +01:00
parent 52209c476b
commit 1284371bb3
52 changed files with 250 additions and 563 deletions
+81
View File
@@ -0,0 +1,81 @@
# Corteza codegen tools and definitions
## History
See [old codegen](../pkh/codegen/README.md).
## Plans
Right now, Corteza is migrating its old YAML definitions to CUE.
We are also simplifying all templates by moving as much data manipulation to Cue as possible.
## Intro
Codegen tools are based on [cuelang](https://cuelang.org/docs/tutorials/) and golang templates.
**What you can find here:**
- [codegen definitions in `codegen/`](./)
- [templates in `codegen/asset/templates`](./asset/templates)
- [schemas in `codegen/schema`](./schema)
- [template exec tool in `codegen/tool`](./tool)
Platform, component and resource definitions (.cue files) can be found in:
- `app`
- `automation` @todo
- `system`
- `compose`
- `federation` @todo
## Running code generator
When a definitions or templates are changed all outputs need to be regenerated
This can be done with the following command
```
make codegen
```
Please note that
## How does it work?
### High-level overview
See [Makefile's `codegen-cue` task](../Makefile)
1. evaluate codegen instructions (see [platform.cue](./platform.cue))
2. output instructions as JSON
3. pipe JSON into [template exec tool](./tool)
4. process instructions, load and execute templates and write to output files.
### Definition structure
#### Codegen instructions
Collection of `#codegen` structs with template + payload + output instructions. Template exec tool iterates over collection and creates output from each one.
#### Platform
Main entry point that combines all components
- @todo options
- @todo REST endpoints (unrelated to specific component)
#### Component
Defines component, it's behaviour, RBAC operations and resources
- @todo REST endpoints (unrelated to specific resources)
#### Resource
Defines resource, it's behaviour, types, RBAC operations, translatable keys
- @todo events
- @todo actions
- @todo errors
- @todo automation functions
- @todo expression types
- @todo REST endpoints
+22 -22
View File
@@ -95,8 +95,8 @@ envoyRBAC:
resources: [
for cmp in app.corteza.components for res in cmp.resources if res.locale != _|_ {
importAlias: "\(cmp.ident)Types"
typeConst: "\(importAlias).\(res.expIdent)ResourceTranslationType"
importAlias: "\(cmp.ident)Types"
typeConst: "\(importAlias).\(res.expIdent)ResourceTranslationType"
resTrRefFunc: "\(cmp.expIdent)\(res.expIdent)ResourceTranslationReferences"
references: [
for p in res.parents {p},
@@ -110,25 +110,25 @@ envoyRBAC:
[
// wrapped with additional for loop to trim out templates with empty types list
for tpl in [
for cmp in app.corteza.components {
template: "gocode/envoy/resource_translation_references_$component.go.tpl"
output: "pkg/envoy/resource/resource_translation_references_\(cmp.ident).gen.go"
payload: {
package: "resource"
imports: [
"\"github.com/cortezaproject/corteza-server/\(cmp.ident)/types\"",
]
for cmp in app.corteza.components {
template: "gocode/envoy/resource_translation_references_$component.go.tpl"
output: "pkg/envoy/resource/resource_translation_references_\(cmp.ident).gen.go"
payload: {
package: "resource"
imports: [
"\"github.com/cortezaproject/corteza-server/\(cmp.ident)/types\"",
]
resources: [
for res in cmp.resources if res.locale != _|_ {
resTrRefFunc: "\(cmp.expIdent)\(res.expIdent)ResourceTranslationReferences"
expIdent: res.expIdent
references: [
for p in res.parents {p},
]
},
]
}
},
] if len(tpl.payload.resources) > 0 {tpl}]+
resources: [
for res in cmp.resources if res.locale != _|_ {
resTrRefFunc: "\(cmp.expIdent)\(res.expIdent)ResourceTranslationReferences"
expIdent: res.expIdent
references: [
for p in res.parents {p},
]
},
]
}
},
] if len(tpl.payload.resources) > 0 {tpl}]+
[]
+28
View File
@@ -0,0 +1,28 @@
package schema
import (
"strings"
)
#component: #_base & {
// copy field values from #_base
handle: handle, ident: ident, expIdent: expIdent
label: strings.ToTitle(ident)
platform: #baseHandle
resources: {
[key=_]: {"handle": key, "component": handle, "platform": platform} & #resource
}
fqrn: platform + "::" + handle
// All known RBAC operations for this component
rbac: #rbacComponent & {
operations: {
grant: {
description: "Manage \(handle) permissions"
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
package schema
import (
"strings"
"list"
)
#locale: {
resourceExpIdent: #expIdent
// @todo we need a better name here!
skipSvc: bool | *false
extended: bool | *false
resource: {
// @todo merge with RBAC res-ref and move 2 levels lower.
references: [ ...string] | *["ID"]
type: string
const: string | *("\(resourceExpIdent)ResourceTranslationType")
}
keys: {
[key=_]: #localeKey & {
name: key
_resourceExpIdent: resourceExpIdent
}
}
}
#localeKey: {
name: #handle
_resourceExpIdent: #expIdent
path: [...(#ident | {part: #ident, var: bool | *false})] | *([name])
expandedPath: [ for p in path {
if (p & {"p": #ident}) != _|_ {p, var: p.var}
if (p & string) != _|_ {"part": p, var: false}
}]
_suffix: strings.Join([ for p in expandedPath {strings.ToTitle(p.part)}], "")
struct: string | *("LocaleKey" + _resourceExpIdent + _suffix)
// As soon as we use vars in the path,
// custom handler must be present
_hasVars: list.Contains([ for p in path {p.var | false}], true)
customHandler: bool | *_hasVars
if customHandler {
decodeFunc: string | *("decodeTranslations" + _suffix)
encodeFunc: string | *("encodeTranslations" + _suffix)
serviceFunc: string | *("handle" + _resourceExpIdent + _suffix)
}
}
+17
View File
@@ -0,0 +1,17 @@
package schema
#platform: {
ident: #baseHandle | *"corteza"
components: [...{platform: ident} & #component]
// env-var definitions
// options: {}
//
// automation: {
// types: ....
// function ....
// }
}
+56
View File
@@ -0,0 +1,56 @@
package schema
import (
"strings"
)
#rbacComponent: {
resource: {
type: string
}
operations: {
[key=_]: #rbacOperation & {
handle: key
}
}
}
#rbacResource: {
resourceExpIdent: #expIdent
operations: {
[key=_]: #rbacOperation & {
handle: key
_resourceExpIdent: resourceExpIdent
}
}
}
#rbacOperation: {
handle: #handle
description: string | *handle
_resourceExpIdent?: string
// Some string manipulation that will result in
// more pronouncable access-control check function name
// When check function name is not explicitly defined we try
// to use resource and operation name and generate easy-to-read name
//
// <res> + <op> => Can<Op><Res>
// <res> + <op:foo.bar.verb> => Can<Verb><Foo><Bar>On<Res>
_operation: strings.Replace(strings.Replace(handle, "-", " ", -1), "_", " ", -1)
_opSplit: strings.Split(_operation, ".")
_opFlip: [_opSplit[len(_opSplit)-1]] + _opSplit[0:len(_opSplit)-1]
_opFinal: strings.Replace(strings.ToTitle(strings.Join(_opFlip, " ")), " ", "", -1)
if _resourceExpIdent == _|_ {
checkFuncName: #expIdent | *("Can" + _opFinal)
}
if _resourceExpIdent != _|_ {
checkFuncName: #expIdent | *("Can" + _opFinal + _resourceExpIdent)
}
}
+98
View File
@@ -0,0 +1,98 @@
package schema
#resource: #_base & {
// copy field values from #_base
handle: handle, ident: ident, expIdent: expIdent
component: #baseHandle | *"component"
platform: #baseHandle | *"corteza"
// Fully qualified resource name
fqrn: string | *(platform + "::" + component + ":" + handle)
// fields: #Fields
// operations: #Operations
// All parent resources
parents: [... #_base & {
// copy field values from #_base
handle: handle, ident: ident, expIdent: expIdent
refField: #expIdent | *(expIdent + "ID")
param: #ident | *(ident + "ID")
}]
// All known RBAC operations for this resource
rbac: #rbacResource & {
resourceExpIdent: expIdent
}
locale?: #locale & {
resourceExpIdent: expIdent
resource: {
// @todo can we merge this with RBAC type (FQRN?)
type: component + ":" + handle
}
}
// List of known keys for resource translation
// locale?: {
// [Name=_]: {
// name: Name & #Handle
// path: string
// custom: bool | *false
// }
// }
}
//#fields: {
// // Each field can be
// [key=_]: #fields | *({name: key} & #field)
//}
//
//#field: {
// name: #expIdent
// unique: bool | *false
//
// // Golang type (built-in or other)
// type: string | *"string"
//
// // System fields,
// system: bool | *false
//
// if name =~ "At$" {
// type: string | *"*time.Time"
// }
//}
//#Operations: {
// [Operation=_]: {operation: Operation} & #Operation
//}
//#Operation: {
// name: #ExpIdent
// description: string
// can: string | false | *"\(name)"
//}
//idField: {
// // Expecting ID field to allways have name ID
// name: "ID"
// unique: true
//
// // Service fields,
// // @todo We might want to have a better name for this
// // service: true
//
// // @todo someday we'll replace this with the "ID" type
// type: "uint64"
//}
//
//handleField: {
// // Expecting ID field to allways have name ID
// name: "handle"
// unique: true
//
// // @todo someday we'll replace this with the "ID" type
// type: "string" & #handle
//}
+32
View File
@@ -0,0 +1,32 @@
package schema
import (
"strings"
)
// Identifier
#ident: =~"^[a-z][a-zA-Z0-9_]*$"
// Exported identifier
#expIdent: =~"^[A-Z][a-zA-Z0-9_]*$"
// More liberal then identifier, allows underscores and dots
#handle: =~"^[A-Za-z][a-zA-Z0-9_\\-\\.]*[a-zA-Z0-9]+$"
// More liberal then identifier, allows underscores and dots
#baseHandle: =~"^[a-z][a-z0-9-]*[a-z0-9]+$"
#_base: {
// lowercase dash-separated words
// used to build ident and exported identifiers
handle: #baseHandle | *"base"
_words: strings.Replace(strings.Replace(strings.Replace(handle, "-", " ", -1), "_", " ", -1), ".", " ", -1)
// lowercased (unexported, golang) identifier
ident: #ident | *strings.ToCamel(strings.Replace(strings.ToTitle(_words), " ", "", -1))
// upercased (exported, golang) identifier
expIdent: #expIdent | *strings.Replace(strings.ToTitle(_words), " ", "", -1)
...
}
+4
View File
@@ -24,6 +24,10 @@ func LoadTemplates(rTpl *template.Template, rootDir string) (*template.Template,
pfx := len(cleanRoot) + 1
return rTpl, filepath.Walk(cleanRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasSuffix(path, ".tpl") || err != nil {
return err
}