System wide RBAC changes

This commit is contained in:
Denis Arh
2021-07-08 11:42:18 +02:00
parent a1de3374ad
commit 20e05280b3
223 changed files with 5500 additions and 3263 deletions
@@ -0,0 +1,35 @@
package {{ .Package }}
{{ template "header-gentext.tpl" }}
{{ template "header-definitions.tpl" . }}
import (
{{- range .Imports }}
{{ . }}
{{- end }}
)
{{- range .Def }}
{{- if gt (len .RBAC.Resource.References) 0 }}
// {{ export .Component .Resource }}RbacReferences generates RBAC references
//
// Resources with "envoy: false" are skipped
//
// This function is auto-generated
func {{ export .Component .Resource }}RbacReferences({{- range .RBAC.Resource.References }}{{ unexport .Resource }} string, {{- end }}) (res *Ref, pp []*Ref, err error) {
{{- range .RBAC.Resource.References }}
{{- if eq .Field "ID" }}
if {{ unexport .Resource }} != "*" {
res = &Ref{ResourceType: types.{{ export .Resource }}ResourceType, Identifiers: MakeIdentifiers({{ unexport .Resource }})}
}
{{- else }}
if {{ unexport .Resource }} != "*" {
pp = append(pp, &Ref{ResourceType: types.{{ export .Resource }}ResourceType, Identifiers: MakeIdentifiers({{ unexport .Resource }})})
}
{{- end }}
{{- end }}
return
}
{{- end }}
{{- end }}
@@ -0,0 +1,71 @@
package {{ .Package }}
{{ template "header-gentext.tpl" }}
{{ template "header-definitions.tpl" . }}
import (
"fmt"
"strings"
{{- range .Imports }}
{{ . }}
{{- end }}
)
// Parse generates resource setting logic for each resource
//
// Resources with "envoy: false" are skipped
//
// This function is auto-generated
func ParseRule(res string) (string, *Ref, []*Ref, error) {
if res == "" {
return "", nil, nil, fmt.Errorf("empty resource")
}
sp := "/"
res = strings.TrimSpace(res)
res = strings.TrimRight(res, sp)
rr := strings.Split(res, sp)
// only service defined (corteza::system, corteza::compose, ...)
if len(rr) == 1 {
return res, nil, nil, nil
}
// full thing
resourceType, path := rr[0], rr[1:]
for p := 1; p < len(path); p++ {
if path[p] != "*" && path[p-1] == "*" {
return "", nil, nil, fmt.Errorf("invalid path wildcard combination for '%s'", res)
}
}
// make the resource provide the slice of parent resources we should nest under
switch resourceType {
{{- range .Def }}
case {{ unexport .Component "types" }}.{{ export .Resource }}ResourceType:
if len(path) != {{ len .RBAC.Resource.References }} {
return "", nil, nil, fmt.Errorf("expecting {{ len .RBAC.Resource.References }} reference components in path, got %d", len(path))
}
{{- if gt (len .RBAC.Resource.References) 0 }}
ref, pp, err := {{ export .Component .Resource }}RbacReferences(
{{- range $i, $r := .RBAC.Resource.References }}
// {{ unexport $r.Resource }}
path[{{ $i }}],
{{ end }}
)
return {{ unexport .Component "types" }}.{{ export .Resource }}ResourceType, ref, pp, err
{{ else }}
// Component resource, no path
return {{ unexport .Component "types" }}.{{ export .Resource }}ResourceType, nil, nil, nil
{{- end }}
{{- end}}
}
// return unhandled resource as-is
return resourceType, nil, nil, nil
}
@@ -0,0 +1,4 @@
// Definitions file that controls how this file is generated:
{{- range .Def }}
// - {{ .Source }}
{{- end }}
@@ -0,0 +1,5 @@
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
@@ -0,0 +1,256 @@
package {{ .Package }}
{{ template "header-gentext.tpl" }}
{{ template "header-definitions.tpl" . }}
import (
"fmt"
"github.com/spf13/cast"
"strings"
"context"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
{{- range .Imports }}
{{ . }}
{{- end }}
)
type (
accessControl struct {
actionlog actionlog.Recorder
rbac interface {
Can(rbac.Session, string, rbac.Resource) bool
Grant(context.Context, ...*rbac.Rule) error
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
}
}
)
func AccessControl() *accessControl {
return &accessControl{
rbac: rbac.Global(),
actionlog: DefaultActionlog,
}
}
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
}
// Effective returns a list of effective permissions for all given resource
func (svc accessControl) Effective(ctx context.Context, rr ... rbac.Resource) (ee rbac.EffectiveSet) {
for _, res := range rr {
r := res.RbacResource()
for op := range rbacResourceOperations(r) {
ee.Push(r, op, svc.can(ctx, op, res))
}
}
return
}
func (svc accessControl) List() (out []map[string]string) {
def := []map[string]string{
{{- range .Def }}
{{- $Resource := .Resource }}
{{- $RbacResource := .RBAC.Resource }}
{{- range .RBAC.Operations }}
{
"type": types.{{ coalesce $Resource }}ResourceType,
"any": types.{{ coalesce $Resource }}RbacResource({{ range $RbacResource.References }}0,{{ end }}),
"op": {{ printf "%q" .Operation }},
},
{{- end }}
{{- end }}
}
func(svc interface{}) {
if svc, is := svc.(interface{}).(interface{ list() []map[string]string }); is {
def = append(def, svc.list()...)
}
}(svc)
return def
}
// Grant applies one or more RBAC rules
//
// This function is auto-generated
func (svc accessControl) Grant(ctx context.Context, rr ...*rbac.Rule) error {
if !svc.CanGrant(ctx) {
// @todo should be altered to check grant permissions PER resource
return AccessControlErrNotAllowedToSetPermissions()
}
for _, r := range rr {
err := rbacResourceValidator(r.Resource, r.Operation)
if err != nil {
return err
}
}
if err := svc.rbac.Grant(ctx, rr...); err != nil {
return AccessControlErrGeneric().Wrap(err)
}
svc.logGrants(ctx, rr)
return nil
}
// This function is auto-generated
func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
if svc.actionlog == nil {
return
}
for _, r := range rr {
g := AccessControlActionGrant(&accessControlActionProps{r})
g.log = r.String()
g.resource = r.Resource
svc.actionlog.Record(ctx, g.ToAction())
}
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) (rbac.RuleSet, error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
return svc.rbac.FindRulesByRoleID(roleID), nil
}
{{- range .Def }}
{{ $GoType := printf "types.%s" (.Resource) }}
{{ if .IsComponentResource }}
{{- range .RBAC.Operations }}
// {{ export .CanFnName }} checks if current user can {{ lower .Description }}
//
// This function is auto-generated
func (svc accessControl) {{ export .CanFnName }}(ctx context.Context) bool {
return svc.can(ctx, {{ printf "%q" .Operation }}, &types.Component{})
}
{{- end }}
{{ else }}
{{- range .RBAC.Operations }}
// {{ export .CanFnName }} checks if current user can {{ lower .Description }}
//
// This function is auto-generated
func (svc accessControl) {{ export .CanFnName }}(ctx context.Context, r * {{ $GoType }}) bool {
return svc.can(ctx, {{ printf "%q" .Operation }}, r)
}
{{- end }}
{{ end }}
{{- end }}
// rbacResourceValidator validates known component's resource by routing it to the appropriate validator
//
// This function is auto-generated
func rbacResourceValidator(r string, oo ...string) error {
switch rbac.ResourceType(r) {
{{- range .Def }}
case types.{{ coalesce .Resource }}ResourceType:
return rbac{{ .Resource }}ResourceValidator(r, oo...)
{{- end }}
}
return fmt.Errorf("unknown resource type '%q'", r)
}
// rbacResourceOperations returns defined operations for a requested resource
//
// This function is auto-generated
func rbacResourceOperations(r string) map[string]bool {
switch rbac.ResourceType(r) {
{{- range .Def }}
case types.{{ coalesce .Resource }}ResourceType:
return map[string]bool{
{{- range .RBAC.Operations }}
{{ printf "%q" .Operation }}: true,
{{- end }}
}
{{- end }}
}
return nil
}
{{- range .Def }}
{{ $Resource := .Resource }}
{{ $GoType := printf "types.%s" (.Resource) }}
// rbac{{ .Resource }}ResourceValidator checks validity of rbac resource and operations
//
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbac{{ .Resource }}ResourceValidator(r string, oo ...string) error {
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for {{ .Component }}{{ if not .IsComponentResource }} {{ .Resource }}{{end }} resource", o)
}
}
if !strings.HasPrefix(r, {{ $GoType }}ResourceType) {
// expecting resource to always include path
return fmt.Errorf("invalid resource type")
}
{{ if .RBAC.Resource.References }}
const sep = "/"
var (
specIdUsed = true
pp = strings.Split(strings.Trim(r[len({{ $GoType }}ResourceType):], sep), sep)
prc = []string{
{{- range .RBAC.Resource.References }}
{{ printf "%q" .Field }},
{{- end }}
}
)
if len(pp) != len(prc) {
return fmt.Errorf("invalid resource path structure")
}
for i, p := range pp {
if p == "*" {
if !specIdUsed {
return fmt.Errorf("invalid resource path wildcard level (%d) for {{ .Resource }}", i)
}
specIdUsed = false
continue
}
specIdUsed = true
if _, err := cast.ToUint64E(p); err != nil {
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p)
}
}
{{- end }}
return nil
}
{{- end }}
@@ -0,0 +1,99 @@
package {{ .Package }}
{{ template "header-gentext.tpl" }}
{{ template "header-definitions.tpl" . }}
import (
"fmt"
"strconv"
)
type (
// Component struct serves as a virtual resource type for the {{ .Component }} component
//
// This struct is auto-generated
Component struct {}
)
const (
{{- range .Def }}
{{ coalesce .Resource "Component" }}ResourceType = "{{ .RBAC.ResourceType }}"
{{- end }}
)
{{- range .Def }}
{{ $Resource := .Resource }}
{{ $GoType := printf "types.%s" .Resource }}
// RbacResource returns string representation of RBAC resource for {{ .Resource }} by calling {{ .Resource }}RbacResource fn
//
// RBAC resource is in the {{ .RBAC.ResourceType }}/... format
//
// This function is auto-generated
func (r {{ .Resource }}) RbacResource() string {
return {{ .Resource }}RbacResource({{ if .RBAC.Resource }}{{ range .RBAC.Resource.References }}r.{{ export .Field }},{{ end }}{{ end }})
}
// {{ .Resource }}RbacResource returns string representation of RBAC resource for {{ .Resource }}
//
// RBAC resource is in the {{ .RBAC.ResourceType }}/{{- if .RBAC.Resource.References }}...{{ end }} format
//
// This function is auto-generated
func {{ .Resource }}RbacResource({{ if .RBAC.Resource }}{{ range .RBAC.Resource.References }}{{ unexport .Field }} uint64,{{ end }}{{ end }}) string {
{{- if .RBAC.Resource.References }}
cpts := []interface{{"{}"}}{{"{"}}{{ .Resource }}ResourceType{{"}"}}
{{- range .RBAC.Resource.References }}
if {{ unexport .Field }} != 0 {
cpts = append(cpts, strconv.FormatUint({{ unexport .Field }}, 10))
} else {
cpts = append(cpts, "*")
}
{{ end }}
return fmt.Sprintf({{ .Resource }}RbacResourceTpl(), cpts...)
{{- else }}
return {{ .Resource }}ResourceType + "/"
{{- end }}
}
// @todo template
func {{ .Resource }}RbacResourceTpl() string {
{{- if .RBAC.Resource.References }}
return "%s
{{- range .RBAC.Resource.References }}/%s{{- end }}"
{{- else }}
return "%s"
{{- end }}
}
{{ if .RBAC.Resource.Attributes }}
// RbacAttributes returns resource attributes used for generating list of contextual roles
//
// This function is auto-generated
func (r {{ .Resource }}) RbacAttributes() map[string]interface{} {
return {{ unexport .Resource }}RbacAttributes(r)
}
{{ if .RBAC.Resource.Attributes.Fields }}
// {{ .Resource }}RbacResource returns string representation of RBAC resource for {{ .Resource }}
//
// RBAC resource is in the {{ .RBAC.ResourceType }}/... format
//
// This function is auto-generated
func {{ unexport .Resource }}RbacAttributes(r {{ .Resource }}) map[string]interface{} {
return map[string]interface{}{
{{- range .RBAC.Resource.Attributes.Fields }}
{{ printf "%q" . }}: r.{{ export . }},
{{- end }}
}
}
{{- end }}
{{- end }}
{{- end }}
+86
View File
@@ -0,0 +1,86 @@
package def
import (
"fmt"
"strings"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl"
"github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
type (
Document struct {
Skip bool `yaml:"(skip)"`
Imports []string
Component string
IsComponentResource bool `yaml:"-"`
Resource string
Source string
RBAC *rbac
Envoy bool `yaml:"envoy"`
}
)
func (set *rbacOperations) UnmarshalYAML(n *yaml.Node) error {
return y7s.Each(n, func(k *yaml.Node, v *yaml.Node) (err error) {
def := rbacOperation{}
if k != nil {
def.Operation = k.Value
}
*set = append(*set, &def)
return v.Decode(&def)
})
}
// Preproc preprocesses the document and sets defaults
func (doc *Document) Proc(filename string) error {
doc.Source = filename
// filename parts
fp := strings.Split(filename, ".")
// trim extension
fp = fp[:len(fp)-1]
if len(fp) > 0 && doc.Component == "" {
// set component from the 1st part
// component is system, compose, ...
doc.Component = fp[0]
}
if len(fp) > 1 && doc.Resource == "" {
// if there are more parts, set resource
// resource is user, module, record, workflow, ...
doc.Resource = fp[1]
}
if strings.ToLower(doc.Resource) == "component" {
return fmt.Errorf("can not use 'component' as a resource name, this is done automatically")
} else if doc.Resource == "" {
doc.Resource = "component"
doc.IsComponentResource = true
}
doc.Imports = normalizeImport(doc.Imports...)
if err := doc.RBAC.proc(doc.Component, doc.Resource); err != nil {
return err
}
doc.Resource = tpl.Export(doc.Resource)
return nil
}
func normalizeImport(ii ...string) []string {
for i := range ii {
if strings.Contains(ii[i], " ") {
p := strings.SplitN(ii[i], " ", 2)
ii[i] = fmt.Sprintf(`%s "%s"`, p[0], strings.Trim(p[1], `"`))
} else {
ii[i] = fmt.Sprintf(`"%s"`, strings.Trim(ii[i], `"'`+"`"))
}
}
return ii
}
+165
View File
@@ -0,0 +1,165 @@
package def
import (
"fmt"
"strings"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl"
"github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
type (
rbac struct {
// fully qualified resource name
ResourceType string `yaml:"resourceType"`
Resource *rbacResource
Operations rbacOperations
}
rbacResource struct {
References []*rbacResourceRef
Attributes *rbacAttributes
}
rbacResourceRef struct {
Field string
ResourceType string
Resource string
Component string
custom bool
}
rbacOperations []*rbacOperation
rbacOperation struct {
Operation string
CanFnName string `yaml:"canFnName"`
Description string
}
rbacAttributes struct {
Fields []string `yaml:"-"`
}
)
func (r *rbac) proc(component, resource string) error {
const (
defaultNS = "corteza"
nsDelimiter = "::"
)
if r.ResourceType == "" {
if strings.ToLower(resource) == "component" {
r.ResourceType = component
} else {
r.ResourceType = fmt.Sprintf("%s:%s", component, resource)
}
r.ResourceType = defaultNS + nsDelimiter + r.ResourceType
}
if !strings.Contains(r.ResourceType, nsDelimiter) {
return fmt.Errorf("no namespace prefix found (e.g.: 'corteza::') in resource type")
}
for _, op := range r.Operations {
// Generate all check name
if op.CanFnName == "" {
op.CanFnName = RbacOperationCanFnName(resource, op.Operation)
}
}
if r.Resource == nil {
r.Resource = &rbacResource{References: []*rbacResourceRef{{Field: "ID"}}}
}
// check types of each referenced component
// and prefix non-custom components with corteza::<component>
// and self-references (field==ID) with own resource type
for _, rc := range r.Resource.References {
if !rc.custom {
if rc.Field == "ID" {
rc.ResourceType = r.ResourceType
rc.Component = component
rc.Resource = resource
} else {
rc.ResourceType = defaultNS + nsDelimiter + fmt.Sprintf("%s:%s", component, rc.Field)
rc.Component = component
rc.Resource = rc.Field
rc.Field = rc.Field + "ID"
}
}
}
return nil
}
func (op *rbacOperation) UnmarshalYAML(n *yaml.Node) error {
if y7s.IsKind(n, yaml.ScalarNode) {
// @todo handle disabled operations
// the idea is that when service operations are defined we implicitly define
// RBAC operations. Here, we'll be able to remove implicitly defined operation
return nil
}
type auxType rbacOperation
var aux = (*auxType)(op)
return n.Decode(aux)
}
func (op *rbacResourceRef) UnmarshalYAML(n *yaml.Node) error {
if y7s.IsKind(n, yaml.ScalarNode) {
op.Field = n.Value
if n.Value != "ID" {
op.ResourceType = n.Value
// @todo expand resource & component
}
return nil
}
type auxType rbacResourceRef
var aux = (*auxType)(op)
aux.custom = true
return n.Decode(aux)
}
func (a *rbacAttributes) UnmarshalYAML(n *yaml.Node) error {
if y7s.IsKind(n, yaml.ScalarNode) {
return nil
}
// if not scalar, assume we will get list of fields
a.Fields = make([]string, 0)
return n.Decode(&a.Fields)
}
func RbacOperationCanFnName(res, op string) string {
// 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>
if strings.ToLower(res) == "component" {
res = ""
}
if strings.Contains(op, ".") {
parts := strings.Split(op, ".")
l := len(parts)
parts = append(parts[l-1:], parts[:l-1]...)
if res != "" {
// Only append "on" if there is resource
parts = append(parts, "on")
}
op = tpl.Export(parts...)
}
return tpl.Export("can", op, res)
}
+82
View File
@@ -0,0 +1,82 @@
package gen
import (
"fmt"
"text/template"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/def"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl"
"github.com/cortezaproject/corteza-server/pkg/slice"
)
func Envoy(t *template.Template, dd []*def.Document) error {
return List{
"resource rbac parse": envoyResourceRbacUnmarshal,
"resource rbac references": envoyResourceRbacReferences,
}.Generate(t, dd)
}
// EnvoyResourceRbacUnmarshal envoy rbac unmarshal
// <service>/service/rbac.gen.go
//
// Contains all RBAC related definitions
func envoyResourceRbacUnmarshal(t *template.Template, dd []*def.Document) (err error) {
const (
templateName = "envoy/resource-rbac_rules_parse.go.tpl"
outputPathTpl = "pkg/envoy/resource/rbac_rules_parse.gen.go"
)
dd = filter(dd, func(d *def.Document) bool { return d.Envoy })
// build list of component type imports
ctImports := make([]string, 0)
for _, d := range dd {
imp := d.Component + "Types " + cImport(d.Component, "types")
if !slice.HasString(ctImports, imp) {
ctImports = append(ctImports, imp)
}
}
w := tpl.Wrap{
Package: "resource",
Def: dd,
Imports: append(collectImports(dd...), ctImports...),
}
err = tpl.GoTemplate(outputPathTpl, t.Lookup(templateName), w)
if err != nil {
return
}
return
}
// EnvoyResourceRbacReferences generates one rbac definition file per component
// <service>/service/rbac.gen.go
//
// Contains all RBAC related definitions
func envoyResourceRbacReferences(t *template.Template, dd []*def.Document) (err error) {
const (
templateName = "envoy/resource-rbac_references.go.tpl"
outputPathTpl = "pkg/envoy/resource/rbac_references_%s.gen.go"
)
dd = filter(dd, func(d *def.Document) bool { return d.Envoy })
for component, perComponent := range partByComponent(dd) {
w := tpl.Wrap{
Package: "resource",
Component: component,
Def: perComponent,
Imports: append(collectImports(perComponent...), cImport(component, "types")),
}
err = tpl.GoTemplate(fmt.Sprintf(outputPathTpl, component), t.Lookup(templateName), w)
if err != nil {
return
}
}
return
}
+67
View File
@@ -0,0 +1,67 @@
package gen
import (
"fmt"
"text/template"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/def"
)
type (
List map[string]func(*template.Template, []*def.Document) error
)
func (gg List) Generate(tpls *template.Template, dd []*def.Document) (err error) {
for l, g := range gg {
if err = g(tpls, dd); err != nil {
return fmt.Errorf("codegen for %s failed: %w", l, err)
}
}
return
}
func filter(dd []*def.Document, check func(*def.Document) bool) []*def.Document {
aux := make([]*def.Document, 0, len(dd))
for _, d := range dd {
if !check(d) {
continue
}
aux = append(aux, d)
}
return aux
}
func partByComponent(dd []*def.Document) map[string][]*def.Document {
var (
parted = make(map[string][]*def.Document)
)
for _, d := range dd {
parted[d.Component] = append(parted[d.Component], d)
}
return parted
}
func collectImports(dd ...*def.Document) []string {
mm := make(map[string]bool)
for _, d := range dd {
for _, i := range d.Imports {
mm[i] = true
}
}
ii := make([]string, 0, len(mm))
for i := range mm {
ii = append(ii, i)
}
return ii
}
// component import
func cImport(c, s string) string {
return fmt.Sprintf(`"github.com/cortezaproject/corteza-server/%s/%s"`, c, s)
}
+69
View File
@@ -0,0 +1,69 @@
package gen
import (
"fmt"
"text/template"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/def"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl"
)
func RBAC(t *template.Template, dd []*def.Document) error {
return List{
"type": rbacTypes,
"service access control": rbacAccessControlService,
}.Generate(t, dd)
}
// RbacTypes generates rbac definitions (one per component)
// <service>/service/rbac.gen.go
//
// Contains all RBAC related definitions
func rbacTypes(t *template.Template, dd []*def.Document) (err error) {
const (
templateName = "rbac/types.go.tpl"
outputPathTpl = "%s/types/rbac.gen.go"
)
for component, perComponent := range partByComponent(dd) {
w := tpl.Wrap{
Package: "types",
Component: component,
Def: perComponent,
}
err = tpl.GoTemplate(fmt.Sprintf(outputPathTpl, component), t.Lookup(templateName), w)
if err != nil {
return
}
}
return
}
// RbacAccessControlService generates access control functions (one file per component)
// <service>/service/rbac.gen.go
//
// Contains all RBAC related definitions
func rbacAccessControlService(t *template.Template, dd []*def.Document) (err error) {
const (
templateName = "rbac/access_control.go.tpl"
outputPathTpl = "%s/service/access_control.gen.go"
)
for component, perComponent := range partByComponent(dd) {
w := tpl.Wrap{
Package: "service",
Component: component,
Def: perComponent,
Imports: append(collectImports(perComponent...), cImport(component, "types")),
}
err = tpl.GoTemplate(fmt.Sprintf(outputPathTpl, component), t.Lookup(templateName), w)
if err != nil {
return
}
}
return
}
+126
View File
@@ -0,0 +1,126 @@
package tpl
import (
"bytes"
"fmt"
"go/format"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/Masterminds/sprig"
)
type (
Wrap struct {
Package string
// will be set when grouping definitions by component
Component string
Imports []string
Def interface{}
}
)
var nonIdentChars = regexp.MustCompile(`[\s\\/\-.]+`)
func Export(pp ...string) (out string) {
for _, p := range pp {
if len(p) > 1 {
p = strings.ToUpper(p[:1]) + p[1:]
}
if ss := nonIdentChars.Split(p, -1); len(ss) > 1 {
p = Export(ss...)
}
out = out + p
}
return out
}
func Unexport(pp ...string) (out string) {
out = Export(pp...)
if len(out) == 0 {
return
}
if out == "ID" {
return "id"
}
return strings.ToLower(out[:1]) + out[1:]
}
func BaseTemplate() *template.Template {
return template.New("").
Funcs(sprig.TxtFuncMap()).
Funcs(map[string]interface{}{
"export": Export,
"unexport": Unexport,
})
}
func LoadTemplates(rTpl *template.Template, rootDir string) (*template.Template, error) {
cleanRoot := filepath.Clean(rootDir)
pfx := len(cleanRoot) + 1
return rTpl, filepath.Walk(cleanRoot, func(path string, info os.FileInfo, err error) error {
if info.IsDir() || !strings.HasSuffix(path, ".tpl") || err != nil {
return err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
name := path[pfx:]
rTpl, err = rTpl.New(name).Parse(string(b))
return err
})
}
func GoTemplate(dst string, tpl *template.Template, payload Wrap) (err error) {
var output io.WriteCloser
buf := bytes.Buffer{}
if tpl == nil {
return fmt.Errorf("could not find template for %s", dst)
}
if err := tpl.Execute(&buf, payload); err != nil {
return err
}
fmtsrc, err := format.Source(buf.Bytes())
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s fmt warn: %v\n", dst, err)
err = nil
fmtsrc = buf.Bytes()
}
if dst == "" || dst == "-" {
output = os.Stdout
} else {
if output, err = os.Create(dst); err != nil {
return err
}
defer output.Close()
}
if _, err = output.Write(fmtsrc); err != nil {
return err
}
return nil
}
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/cortezaproject/corteza-server/pkg/cli"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/def"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/gen"
"github.com/cortezaproject/corteza-server/pkg/codegen-v3/internal/tpl"
"github.com/davecgh/go-spew/spew"
"gopkg.in/yaml.v3"
)
var _ = spew.Dump
func main() {
dd, err := loadDefinitions(os.Args[1])
cli.HandleError(err)
tpls, err := tpl.LoadTemplates(tpl.BaseTemplate(), "./pkg/codegen-v3/assets/templates/gocode")
if err != nil {
cli.HandleError(fmt.Errorf("could not load templates: %w", err))
}
cli.HandleError(gen.List{
"RBAC": gen.RBAC,
"Envoy": gen.Envoy,
}.Generate(tpls, dd))
}
func loadDefinition(r io.Reader) (*def.Document, error) {
doc := &def.Document{
Envoy: true,
}
return doc, yaml.NewDecoder(r).Decode(doc)
}
func loadDefinitions(path string) (dd []*def.Document, err error) {
var (
fh *os.File
doc *def.Document
files []string
)
files, err = filepath.Glob(path + "/*.yaml")
if err != nil {
return nil, fmt.Errorf("could not load ddefinitions form path '%s': %w", path, err)
}
for _, file := range files {
fh, err = os.Open(file)
if err != nil {
return nil, fmt.Errorf("could not load definiton file '%s': %w", file, err)
}
doc, err = loadDefinition(fh)
if err != nil {
return nil, fmt.Errorf("could not load definiton from '%s': %w", file, err)
}
if doc.Skip {
continue
}
if err = doc.Proc(filepath.Base(file)); err != nil {
return nil, fmt.Errorf("failed to preprocess definitions from '%s': %w", file, err)
}
dd = append(dd, doc)
}
return
}