Extend envoy to support resource translations
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package {{ .Package }}
|
||||
|
||||
{{ template "header-gentext.tpl" }}
|
||||
{{ template "header-definitions.tpl" . }}
|
||||
|
||||
import (
|
||||
systemTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
{{- range .Imports }}
|
||||
{{ . }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
{{- range .Def }}
|
||||
{{ $Component := .Component }}
|
||||
{{ $Resource := .Resource }}
|
||||
{{ $GoType := printf "types.%s" .Resource }}
|
||||
func (r *{{export $Component}}{{$Resource}}) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
{{ if .Locale.Extended }}
|
||||
tmp, err := r.encodeTranslations()
|
||||
return append(out, tmp...), err
|
||||
{{ else }}
|
||||
return out, nil
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,73 @@
|
||||
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 ParseResourceTranslation(res string) (string, *Ref, []*Ref, error) {
|
||||
if res == "" {
|
||||
return "", nil, nil, fmt.Errorf("empty resource")
|
||||
}
|
||||
|
||||
sp := "/"
|
||||
|
||||
if strings.Index(res, "corteza::") == 0 {
|
||||
res = res[9:]
|
||||
}
|
||||
|
||||
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 "", nil, nil, fmt.Errorf("only service defined: %s", res)
|
||||
}
|
||||
|
||||
// full thing
|
||||
resourceType, path := rr[0], rr[1:]
|
||||
for p := 1; p < len(path); p++ {
|
||||
if path[p] == "*" {
|
||||
return "", nil, nil, fmt.Errorf("path wildcard not allowed for locale resources: '%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 }}ResourceTranslationType:
|
||||
if len(path) != {{ len .Locale.Resource.References }} {
|
||||
return "", nil, nil, fmt.Errorf("expecting {{ len .Locale.Resource.References }} reference components in path, got %d", len(path))
|
||||
}
|
||||
{{- if gt (len .Locale.Resource.References) 0 }}
|
||||
ref, pp, err := {{ export .Component .Resource }}ResourceTranslationReferences(
|
||||
{{- range $i, $r := .Locale.Resource.References }}
|
||||
// {{ unexport $r.Resource }}
|
||||
path[{{ $i }}],
|
||||
{{ end }}
|
||||
)
|
||||
return {{ unexport .Component "types" }}.{{ export .Resource }}ResourceTranslationType, ref, pp, err
|
||||
{{ else }}
|
||||
|
||||
// Component resource, no path
|
||||
return {{ unexport .Component "types" }}.{{ export .Resource }}ResourceTranslationType, nil, nil, nil
|
||||
{{- end }}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
// return unhandled resource as-is
|
||||
return resourceType, nil, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package {{ .Package }}
|
||||
|
||||
{{ template "header-gentext.tpl" }}
|
||||
{{ template "header-definitions.tpl" . }}
|
||||
|
||||
import (
|
||||
{{- range .Imports }}
|
||||
{{ . }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
{{- range .Def }}
|
||||
{{- if .Locale }}
|
||||
{{- if gt (len .Locale.Resource.References) 0 }}
|
||||
// {{ export .Component .Resource }}ResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ export .Component .Resource }}ResourceTranslationReferences({{- range .Locale.Resource.References }}{{ unexport .Resource }} string, {{- end }}) (res *Ref, pp []*Ref, err error) {
|
||||
{{- range .Locale.Resource.References }}
|
||||
{{- if eq .Field "ID" }}
|
||||
res = &Ref{ResourceType: types.{{ export .Resource }}ResourceType, Identifiers: MakeIdentifiers({{ unexport .Resource }})}
|
||||
{{- else }}
|
||||
pp = append(pp, &Ref{ResourceType: types.{{ export .Resource }}ResourceType, Identifiers: MakeIdentifiers({{ unexport .Resource }})})
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
return
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -13,6 +13,10 @@ func Envoy(t *template.Template, dd []*def.Document) error {
|
||||
return List{
|
||||
"resource rbac parse": envoyResourceRbacUnmarshal,
|
||||
"resource rbac references": envoyResourceRbacReferences,
|
||||
|
||||
"resource translation": envoyResourceTranslation,
|
||||
"resource translation parse": envoyResourceTranslationUnmarshal,
|
||||
"resource translation references": envoyResourceTranslationReferences,
|
||||
}.Generate(t, dd)
|
||||
}
|
||||
|
||||
@@ -80,3 +84,85 @@ func envoyResourceRbacReferences(t *template.Template, dd []*def.Document) (err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func envoyResourceTranslation(t *template.Template, dd []*def.Document) (err error) {
|
||||
const (
|
||||
templateName = "envoy/resource-resource_translation.go.tpl"
|
||||
outputPathTpl = "pkg/envoy/resource/resource_translation.gen.go"
|
||||
)
|
||||
|
||||
dd = filter(dd, func(d *def.Document) bool { return d.Envoy && d.Locale != nil })
|
||||
|
||||
// build list of component type imports
|
||||
ctImports := make([]string, 0)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func envoyResourceTranslationUnmarshal(t *template.Template, dd []*def.Document) (err error) {
|
||||
const (
|
||||
templateName = "envoy/resource-resource_translation_parse.go.tpl"
|
||||
outputPathTpl = "pkg/envoy/resource/resource_translation_parse.gen.go"
|
||||
)
|
||||
|
||||
dd = filter(dd, func(d *def.Document) bool { return d.Envoy && d.Locale != nil })
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func envoyResourceTranslationReferences(t *template.Template, dd []*def.Document) (err error) {
|
||||
const (
|
||||
templateName = "envoy/resource-resource_translation_references.go.tpl"
|
||||
outputPathTpl = "pkg/envoy/resource/resource_translation_references_%s.gen.go"
|
||||
)
|
||||
|
||||
dd = filter(dd, func(d *def.Document) bool { return d.Envoy && d.Locale != nil })
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
65
pkg/envoy/locale.go
Normal file
65
pkg/envoy/locale.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package envoy
|
||||
|
||||
import "github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
|
||||
// NormalizeResourceTranslations takes the provided resource.ResourceTranslation
|
||||
// and merges duplicates based on the Priority parameter
|
||||
func NormalizeResourceTranslations(rr ...resource.Interface) []resource.Interface {
|
||||
out := make([]resource.Interface, 0, len(rr))
|
||||
locales := make([]*resource.ResourceTranslation, 0, len(rr))
|
||||
|
||||
// - collect all locale resources
|
||||
for _, r := range rr {
|
||||
if l, ok := r.(*resource.ResourceTranslation); ok {
|
||||
locales = append(locales, l)
|
||||
} else {
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
|
||||
// make an index
|
||||
var byResource map[string][2]*resource.ResourceTranslation
|
||||
byResource = make(map[string][2]*resource.ResourceTranslation)
|
||||
|
||||
for _, locale := range locales {
|
||||
pp := byResource[locale.RefResource]
|
||||
pp[locale.Priority] = locale
|
||||
byResource[locale.RefResource] = pp
|
||||
}
|
||||
|
||||
// squash index by priority ascending
|
||||
for _, pp := range byResource {
|
||||
var aux *resource.ResourceTranslation
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, p := range pp {
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if aux == nil {
|
||||
aux = p
|
||||
} else {
|
||||
for _, r := range p.Res {
|
||||
if seen[r.Lang.String()+r.K] {
|
||||
continue
|
||||
}
|
||||
|
||||
aux.Res = append(aux.Res, r)
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range p.Res {
|
||||
if r.K != "" {
|
||||
seen[r.Lang.String()+r.K] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aux != nil {
|
||||
out = append(out, aux)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -27,6 +27,9 @@ func CollectNodes(ii ...interface{}) (nn []resource.Interface, err error) {
|
||||
case resource.Interface:
|
||||
nn = append(nn, c)
|
||||
|
||||
case []resource.Interface:
|
||||
nn = append(nn, c...)
|
||||
|
||||
case Marshaller:
|
||||
if tmp, err := c.MarshalEnvoy(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,7 +2,6 @@ package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -38,10 +37,6 @@ func (r *Application) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *Application) Ref() string {
|
||||
return firstOkString(r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindApplication looks for the app in the resource set
|
||||
func FindApplication(rr InterfaceSet, ii Identifiers) (ap *types.Application) {
|
||||
var apRes *Application
|
||||
|
||||
@@ -73,6 +73,14 @@ func (r *AutomationWorkflow) AddAutomationTrigger(res *types.Trigger) *Automatio
|
||||
return t
|
||||
}
|
||||
|
||||
func (r *AutomationWorkflow) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = nil
|
||||
resource = fmt.Sprintf(types.WorkflowResourceTranslationTpl(), types.WorkflowResourceTranslationType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *AutomationWorkflow) AddAutomationWorkflowStep(res *types.WorkflowStep) *AutomationWorkflowStep {
|
||||
s := &AutomationWorkflowStep{
|
||||
base: &base{},
|
||||
@@ -107,10 +115,6 @@ func (r *AutomationWorkflow) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *AutomationWorkflow) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Meta.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindAutomationWorkflow looks for the workflow in the resource set
|
||||
func FindAutomationWorkflow(rr InterfaceSet, ii Identifiers) (ns *types.Workflow) {
|
||||
var wfRes *AutomationWorkflow
|
||||
|
||||
@@ -44,14 +44,18 @@ func (r *ComposeChart) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *ComposeChart) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
func (r *ComposeChart) RBACPath() []*Ref {
|
||||
return []*Ref{r.RefNs}
|
||||
}
|
||||
|
||||
func (r *ComposeChart) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = []*Ref{r.RefNs}
|
||||
resource = fmt.Sprintf(types.ChartResourceTranslationTpl(), types.ChartResourceTranslationType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FindComposeChart looks for the chart in the resources
|
||||
func FindComposeChart(rr InterfaceSet, ii Identifiers) (ch *types.Chart) {
|
||||
var chRes *ComposeChart
|
||||
|
||||
@@ -5,12 +5,23 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
systemTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
ComposeModuleField struct {
|
||||
*base
|
||||
Res *types.ModuleField
|
||||
|
||||
RefNs *Ref
|
||||
RefMod *Ref
|
||||
}
|
||||
|
||||
ComposeModule struct {
|
||||
*base
|
||||
Res *types.Module
|
||||
Res *types.Module
|
||||
ResFields []*ComposeModuleField
|
||||
|
||||
// Might keep track of related NS
|
||||
RefNs *Ref
|
||||
@@ -59,8 +70,25 @@ func (r *ComposeModule) RBACPath() []*Ref {
|
||||
return []*Ref{r.RefNs}
|
||||
}
|
||||
|
||||
func (r *ComposeModule) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
func (r *ComposeModule) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = []*Ref{r.RefNs}
|
||||
resource = fmt.Sprintf(types.ModuleResourceTranslationTpl(), types.ModuleResourceTranslationType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ComposeModule) encodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, len(r.ResFields))
|
||||
|
||||
for _, f := range r.ResFields {
|
||||
rr := f.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := f.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// FindComposeModule looks for the module in the resource set
|
||||
@@ -86,6 +114,93 @@ func FindComposeModule(rr InterfaceSet, ii Identifiers) (ns *types.Module) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ComposeModule) AddField(f *ComposeModuleField) {
|
||||
r.ResFields = append(r.ResFields, f)
|
||||
}
|
||||
|
||||
// func FindComposeModuleField(mod *ComposeModule, ii Identifiers) (f *ComposeModuleField) {
|
||||
// for _, f := range mod.ResFields {
|
||||
// if f.Identifiers().HasAny(ii) {
|
||||
// return f
|
||||
// }
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// FindComposeModuleField looks for the module field in the given module
|
||||
func FindComposeModuleField(mod *types.Module, ii Identifiers) (f *types.ModuleField) {
|
||||
ids := make(map[uint64]bool)
|
||||
handles := make(map[string]bool)
|
||||
for i := range ii {
|
||||
auxID, err := cast.ToUint64E(i)
|
||||
if err == nil {
|
||||
ids[auxID] = true
|
||||
continue
|
||||
}
|
||||
|
||||
handles[i] = true
|
||||
}
|
||||
|
||||
var ok bool
|
||||
ff, _ := mod.Fields.Filter(func(mf *types.ModuleField) (bool, error) {
|
||||
if _, ok = ids[mf.ID]; ok {
|
||||
return true, nil
|
||||
}
|
||||
if _, ok = handles[mf.Name]; ok {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if len(ff) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ff[0]
|
||||
}
|
||||
|
||||
func (r *ComposeModuleField) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = []*Ref{r.RefNs, r.RefMod}
|
||||
resource = fmt.Sprintf(types.ModuleFieldResourceTranslationTpl(), types.ModuleFieldResourceTranslationType, r.RefNs.Identifiers.First(), r.RefMod.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Name))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ComposeModuleField) ResourceTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ComposeModuleErrUnresolved(ii Identifiers) error {
|
||||
return fmt.Errorf("compose module unresolved %v", ii.StringSlice())
|
||||
}
|
||||
|
||||
func ComposeModuleFieldErrUnresolved(ii Identifiers) error {
|
||||
return fmt.Errorf("compose module field unresolved %v", ii.StringSlice())
|
||||
}
|
||||
|
||||
func NewComposeModuleField(res *types.ModuleField, nsRef, modRef string) *ComposeModuleField {
|
||||
r := &ComposeModuleField{
|
||||
base: &base{},
|
||||
}
|
||||
r.SetResourceType(types.ModuleFieldResourceType)
|
||||
r.Res = res
|
||||
|
||||
r.AddIdentifier(identifiers(res.Name, res.ID)...)
|
||||
|
||||
r.RefNs = r.AddRef(types.NamespaceResourceType, nsRef)
|
||||
r.RefMod = r.AddRef(types.ModuleResourceType, modRef)
|
||||
|
||||
// Initial timestamps
|
||||
r.SetTimestamps(MakeTimestampsCUDA(&res.CreatedAt, res.UpdatedAt, res.DeletedAt, nil))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -27,12 +27,16 @@ func NewComposeNamespace(ns *types.Namespace) *ComposeNamespace {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *ComposeNamespace) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
func (r *ComposeNamespace) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = nil
|
||||
resource = fmt.Sprintf(types.NamespaceResourceTranslationTpl(), types.NamespaceResourceTranslationType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Slug))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ComposeNamespace) Ref() string {
|
||||
return firstOkString(r.Res.Slug, r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
func (r *ComposeNamespace) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
// FindComposeNamespace looks for the namespace in the resource set
|
||||
|
||||
@@ -142,14 +142,22 @@ func (r *ComposePage) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *ComposePage) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Title, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
func (r *ComposePage) RBACPath() []*Ref {
|
||||
return []*Ref{r.RefNs}
|
||||
}
|
||||
|
||||
func (r *ComposePage) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) {
|
||||
ref = r.Ref()
|
||||
path = []*Ref{r.RefNs}
|
||||
resource = fmt.Sprintf(types.PageResourceTranslationTpl(), types.PageResourceTranslationType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ComposePage) encodeTranslations() ([]*ResourceTranslation, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FindComposePage looks for the page in the resources
|
||||
func FindComposePage(rr InterfaceSet, ii Identifiers) (pg *types.Page) {
|
||||
var pgRes *ComposePage
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
@@ -188,6 +189,14 @@ func (t *base) Refs() RefSet {
|
||||
return t.rr
|
||||
}
|
||||
|
||||
func (t *base) Ref() *Ref {
|
||||
return &Ref{ResourceType: t.rt, Identifiers: t.ii}
|
||||
}
|
||||
|
||||
func (t *base) HasRefs() bool {
|
||||
return t.rr == nil || len(t.rr) == 0
|
||||
}
|
||||
|
||||
func IgnoreDepResolution(ref *Ref) bool {
|
||||
return ref.ResourceType == composeTypes.ModuleFieldResourceType
|
||||
}
|
||||
|
||||
89
pkg/envoy/resource/resource_translation.gen.go
generated
Normal file
89
pkg/envoy/resource/resource_translation.gen.go
generated
Normal file
@@ -0,0 +1,89 @@
|
||||
package resource
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
// Definitions file that controls how this file is generated:
|
||||
// - automation.workflow.yaml
|
||||
// - compose.chart.yaml
|
||||
// - compose.module-field.yaml
|
||||
// - compose.module.yaml
|
||||
// - compose.namespace.yaml
|
||||
// - compose.page.yaml
|
||||
|
||||
import (
|
||||
systemTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
func (r *AutomationWorkflow) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *ComposeChart) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *ComposeModuleField) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *ComposeModule) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
tmp, err := r.encodeTranslations()
|
||||
return append(out, tmp...), err
|
||||
|
||||
}
|
||||
|
||||
func (r *ComposeNamespace) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *ComposePage) EncodeTranslations() ([]*ResourceTranslation, error) {
|
||||
out := make([]*ResourceTranslation, 0, 10)
|
||||
|
||||
rr := r.Res.EncodeTranslations()
|
||||
rr.SetLanguage(defaultLanguage)
|
||||
res, ref, pp := r.ResourceTranslationParts()
|
||||
out = append(out, NewResourceTranslation(systemTypes.FromLocale(rr), res, ref, pp...))
|
||||
|
||||
tmp, err := r.encodeTranslations()
|
||||
return append(out, tmp...), err
|
||||
|
||||
}
|
||||
54
pkg/envoy/resource/resource_translation.go
Normal file
54
pkg/envoy/resource/resource_translation.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
ResourceTranslation struct {
|
||||
*base
|
||||
Res types.ResourceTranslationSet
|
||||
|
||||
// RelResource string
|
||||
RefResource string
|
||||
RefRes *Ref
|
||||
RefPath []*Ref
|
||||
|
||||
Priority int
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLanguage = language.English
|
||||
)
|
||||
|
||||
func NewResourceTranslation(res types.ResourceTranslationSet, refResource string, refRes *Ref, refPath ...*Ref) *ResourceTranslation {
|
||||
r := &ResourceTranslation{base: &base{}}
|
||||
r.SetResourceType(ResourceTranslationType)
|
||||
r.Res = res
|
||||
|
||||
r.RefResource = refResource
|
||||
r.RefRes = r.AddRef(refRes.ResourceType, refRes.Identifiers.StringSlice()...)
|
||||
|
||||
// any additional constraints
|
||||
for _, rp := range refPath {
|
||||
r.RefPath = append(r.RefPath, r.AddRef(rp.ResourceType, rp.Identifiers.StringSlice()...))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *ResourceTranslation) MarkDefault() {
|
||||
l.Priority = 1
|
||||
}
|
||||
|
||||
func (l *ResourceTranslation) MarkGeneric() {
|
||||
l.Priority = 0
|
||||
}
|
||||
|
||||
func ResourceTranslationErrNotFound(ii Identifiers) error {
|
||||
return fmt.Errorf("resource translation not found %v", ii.StringSlice())
|
||||
}
|
||||
138
pkg/envoy/resource/resource_translation_parse.gen.go
generated
Normal file
138
pkg/envoy/resource/resource_translation_parse.gen.go
generated
Normal file
@@ -0,0 +1,138 @@
|
||||
package resource
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
// Definitions file that controls how this file is generated:
|
||||
// - automation.workflow.yaml
|
||||
// - compose.chart.yaml
|
||||
// - compose.module-field.yaml
|
||||
// - compose.module.yaml
|
||||
// - compose.namespace.yaml
|
||||
// - compose.page.yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
automationTypes "github.com/cortezaproject/corteza-server/automation/types"
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse generates resource setting logic for each resource
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ParseResourceTranslation(res string) (string, *Ref, []*Ref, error) {
|
||||
if res == "" {
|
||||
return "", nil, nil, fmt.Errorf("empty resource")
|
||||
}
|
||||
|
||||
sp := "/"
|
||||
|
||||
if strings.Index(res, "corteza::") == 0 {
|
||||
res = res[9:]
|
||||
}
|
||||
|
||||
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 "", nil, nil, fmt.Errorf("only service defined: %s", res)
|
||||
}
|
||||
|
||||
// full thing
|
||||
resourceType, path := rr[0], rr[1:]
|
||||
for p := 1; p < len(path); p++ {
|
||||
if path[p] == "*" {
|
||||
return "", nil, nil, fmt.Errorf("path wildcard not allowed for locale resources: '%s'", res)
|
||||
}
|
||||
}
|
||||
|
||||
// make the resource provide the slice of parent resources we should nest under
|
||||
switch resourceType {
|
||||
case automationTypes.WorkflowResourceTranslationType:
|
||||
if len(path) != 1 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := AutomationWorkflowResourceTranslationReferences(
|
||||
// workflow
|
||||
path[0],
|
||||
)
|
||||
return automationTypes.WorkflowResourceTranslationType, ref, pp, err
|
||||
|
||||
case composeTypes.ChartResourceTranslationType:
|
||||
if len(path) != 2 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 2 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := ComposeChartResourceTranslationReferences(
|
||||
// namespace
|
||||
path[0],
|
||||
|
||||
// chart
|
||||
path[1],
|
||||
)
|
||||
return composeTypes.ChartResourceTranslationType, ref, pp, err
|
||||
|
||||
case composeTypes.ModuleFieldResourceTranslationType:
|
||||
if len(path) != 3 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 3 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := ComposeModuleFieldResourceTranslationReferences(
|
||||
// namespace
|
||||
path[0],
|
||||
|
||||
// module
|
||||
path[1],
|
||||
|
||||
// moduleField
|
||||
path[2],
|
||||
)
|
||||
return composeTypes.ModuleFieldResourceTranslationType, ref, pp, err
|
||||
|
||||
case composeTypes.ModuleResourceTranslationType:
|
||||
if len(path) != 2 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 2 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := ComposeModuleResourceTranslationReferences(
|
||||
// namespace
|
||||
path[0],
|
||||
|
||||
// module
|
||||
path[1],
|
||||
)
|
||||
return composeTypes.ModuleResourceTranslationType, ref, pp, err
|
||||
|
||||
case composeTypes.NamespaceResourceTranslationType:
|
||||
if len(path) != 1 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := ComposeNamespaceResourceTranslationReferences(
|
||||
// namespace
|
||||
path[0],
|
||||
)
|
||||
return composeTypes.NamespaceResourceTranslationType, ref, pp, err
|
||||
|
||||
case composeTypes.PageResourceTranslationType:
|
||||
if len(path) != 2 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 2 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := ComposePageResourceTranslationReferences(
|
||||
// namespace
|
||||
path[0],
|
||||
|
||||
// page
|
||||
path[1],
|
||||
)
|
||||
return composeTypes.PageResourceTranslationType, ref, pp, err
|
||||
|
||||
}
|
||||
|
||||
// return unhandled resource as-is
|
||||
return resourceType, nil, nil, nil
|
||||
}
|
||||
25
pkg/envoy/resource/resource_translation_references_automation.gen.go
generated
Normal file
25
pkg/envoy/resource/resource_translation_references_automation.gen.go
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
package resource
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
// Definitions file that controls how this file is generated:
|
||||
// - automation.workflow.yaml
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/automation/types"
|
||||
)
|
||||
|
||||
// AutomationWorkflowResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func AutomationWorkflowResourceTranslationReferences(workflow string) (res *Ref, pp []*Ref, err error) {
|
||||
res = &Ref{ResourceType: types.WorkflowResourceType, Identifiers: MakeIdentifiers(workflow)}
|
||||
|
||||
return
|
||||
}
|
||||
78
pkg/envoy/resource/resource_translation_references_compose.gen.go
generated
Normal file
78
pkg/envoy/resource/resource_translation_references_compose.gen.go
generated
Normal file
@@ -0,0 +1,78 @@
|
||||
package resource
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
// Definitions file that controls how this file is generated:
|
||||
// - compose.chart.yaml
|
||||
// - compose.module-field.yaml
|
||||
// - compose.module.yaml
|
||||
// - compose.namespace.yaml
|
||||
// - compose.page.yaml
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
)
|
||||
|
||||
// ComposeChartResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ComposeChartResourceTranslationReferences(namespace string, chart string) (res *Ref, pp []*Ref, err error) {
|
||||
pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespace)})
|
||||
res = &Ref{ResourceType: types.ChartResourceType, Identifiers: MakeIdentifiers(chart)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ComposeModuleFieldResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ComposeModuleFieldResourceTranslationReferences(namespace string, module string, moduleField string) (res *Ref, pp []*Ref, err error) {
|
||||
pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespace)})
|
||||
pp = append(pp, &Ref{ResourceType: types.ModuleResourceType, Identifiers: MakeIdentifiers(module)})
|
||||
res = &Ref{ResourceType: types.ModuleFieldResourceType, Identifiers: MakeIdentifiers(moduleField)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ComposeModuleResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ComposeModuleResourceTranslationReferences(namespace string, module string) (res *Ref, pp []*Ref, err error) {
|
||||
pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespace)})
|
||||
res = &Ref{ResourceType: types.ModuleResourceType, Identifiers: MakeIdentifiers(module)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ComposeNamespaceResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ComposeNamespaceResourceTranslationReferences(namespace string) (res *Ref, pp []*Ref, err error) {
|
||||
res = &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespace)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ComposePageResourceTranslationReferences generates Locale references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func ComposePageResourceTranslationReferences(namespace string, page string) (res *Ref, pp []*Ref, err error) {
|
||||
pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespace)})
|
||||
res = &Ref{ResourceType: types.PageResourceType, Identifiers: MakeIdentifiers(page)}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -32,10 +31,6 @@ func (r *Role) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *Role) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindRole looks for the role in the resources
|
||||
func FindRole(rr InterfaceSet, ii Identifiers) (rl *types.Role) {
|
||||
var rlRes *Role
|
||||
|
||||
@@ -2,7 +2,6 @@ package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -32,10 +31,6 @@ func (r *Template) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *Template) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Meta.Short, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindTemplate looks for the template in the resources
|
||||
func FindTemplate(rr InterfaceSet, ii Identifiers) (u *types.Template) {
|
||||
var tRes *Template
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package resource
|
||||
|
||||
import "strconv"
|
||||
|
||||
type (
|
||||
Interface interface {
|
||||
Identifiers() Identifiers
|
||||
@@ -27,6 +29,13 @@ type (
|
||||
RBACPath() []*Ref
|
||||
}
|
||||
|
||||
LocaleInterface interface {
|
||||
Interface
|
||||
|
||||
ResourceTranslationParts() (string, *Ref, []*Ref)
|
||||
EncodeTranslations() ([]*ResourceTranslation, error)
|
||||
}
|
||||
|
||||
RefSet []*Ref
|
||||
Ref struct {
|
||||
// @todo check with Denis regarding strings here (the cdocs comment)
|
||||
@@ -40,9 +49,10 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
DataSourceResourceType = "data:raw"
|
||||
SettingsResourceType = "setting"
|
||||
RbacResourceType = "rbac-rule"
|
||||
DataSourceResourceType = "data:raw"
|
||||
SettingsResourceType = "setting"
|
||||
RbacResourceType = "rbac-rule"
|
||||
ResourceTranslationType = "resource-translation"
|
||||
)
|
||||
|
||||
func MakeIdentifiers(ss ...string) Identifiers {
|
||||
@@ -87,6 +97,23 @@ func (ri Identifiers) First() string {
|
||||
return ss[0]
|
||||
}
|
||||
|
||||
func (ri Identifiers) FirstID() uint64 {
|
||||
ss := ri.StringSlice()
|
||||
if len(ss) <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, s := range ss {
|
||||
if v, err := strconv.ParseUint(s, 10, 64); err != nil {
|
||||
continue
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rr InterfaceSet) Walk(f func(r Interface) error) (err error) {
|
||||
for _, r := range rr {
|
||||
err = f(r)
|
||||
|
||||
@@ -2,7 +2,6 @@ package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -32,10 +31,6 @@ func (r *User) SysID() uint64 {
|
||||
return r.Res.ID
|
||||
}
|
||||
|
||||
func (r *User) Ref() string {
|
||||
return firstOkString(r.Res.Handle, r.Res.Username, r.Res.Email, r.Res.Name, strconv.FormatUint(r.Res.ID, 10))
|
||||
}
|
||||
|
||||
// FindUser looks for the user in the resources
|
||||
func FindUser(rr InterfaceSet, ii Identifiers) (u *types.User) {
|
||||
var uRes *User
|
||||
|
||||
@@ -16,8 +16,15 @@ func newComposeModule(mod *types.Module) *composeModule {
|
||||
|
||||
func (mod *composeModule) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
refNs := strconv.FormatUint(mod.mod.NamespaceID, 10)
|
||||
refMod := strconv.FormatUint(mod.mod.ID, 10)
|
||||
|
||||
rMod := resource.NewComposeModule(mod.mod, refNs)
|
||||
for _, f := range mod.mod.Fields {
|
||||
r := resource.NewComposeModuleField(f, refNs, refMod)
|
||||
rMod.AddField(r)
|
||||
}
|
||||
|
||||
return envoy.CollectNodes(
|
||||
resource.NewComposeModule(mod.mod, refNs),
|
||||
rMod,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ type (
|
||||
composeChart []*composeChartFilter
|
||||
|
||||
// System stuff
|
||||
roles []*roleFilter
|
||||
users []*userFilter
|
||||
templates []*templateFilter
|
||||
applications []*applicationFilter
|
||||
settings []*settingFilter
|
||||
rbac []*rbacFilter
|
||||
roles []*roleFilter
|
||||
users []*userFilter
|
||||
templates []*templateFilter
|
||||
applications []*applicationFilter
|
||||
settings []*settingFilter
|
||||
rbac []*rbacFilter
|
||||
resourceTranslations []*resourceTranslationFilter
|
||||
|
||||
// Automation stuff
|
||||
automationWorkflow []*automationWorkflowFilter
|
||||
@@ -72,6 +73,22 @@ func (aum auxMarshaller) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Default locales for applicable resources
|
||||
for _, t := range tmp {
|
||||
if li, ok := t.(resource.LocaleInterface); ok {
|
||||
ll, err := li.EncodeTranslations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, l := range ll {
|
||||
l.MarkDefault()
|
||||
ii = append(ii, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ii = append(ii, tmp...)
|
||||
}
|
||||
|
||||
@@ -122,6 +139,7 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
|
||||
system.decodeTemplates(ctx, s, f.templates),
|
||||
system.decodeApplications(ctx, s, f.applications),
|
||||
system.decodeSettings(ctx, s, f.settings),
|
||||
system.decodeResourceTranslation(ctx, s, f.resourceTranslations),
|
||||
|
||||
automation.decodeWorkflows(ctx, s, f.automationWorkflow),
|
||||
)
|
||||
|
||||
@@ -141,6 +141,8 @@ func (se *storeEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
|
||||
err = f(NewSettingFromResource(res, se.cfg), ers)
|
||||
case *resource.RbacRule:
|
||||
err = f(newRbacRuleFromResource(res, se.cfg), ers)
|
||||
case *resource.ResourceTranslation:
|
||||
err = f(newResourceTranslationFromResource(res, se.cfg), ers)
|
||||
|
||||
// Automation resources
|
||||
case *resource.AutomationWorkflow:
|
||||
|
||||
26
pkg/envoy/store/resource_translation.go
Normal file
26
pkg/envoy/store/resource_translation.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
resourceTranslation struct {
|
||||
cfg *EncoderConfig
|
||||
|
||||
locales types.ResourceTranslationSet
|
||||
|
||||
// point to the resource translation
|
||||
refResourceTranslation string
|
||||
refLocaleRes *resource.Ref
|
||||
|
||||
refPathRes []*resource.Ref
|
||||
}
|
||||
)
|
||||
|
||||
func resourceTranslationErrUnidentifiable(ii resource.Identifiers) error {
|
||||
return fmt.Errorf("resource translation unidentifiable %v", ii.StringSlice())
|
||||
}
|
||||
206
pkg/envoy/store/resource_translation_marshal.go
Normal file
206
pkg/envoy/store/resource_translation_marshal.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var (
|
||||
gResourceTranslations map[language.Tag]map[string]*types.ResourceTranslation
|
||||
|
||||
resourceTranslationIndex = func(l *types.ResourceTranslation) string {
|
||||
return fmt.Sprintf("%s/%s", l.Resource, l.K)
|
||||
}
|
||||
)
|
||||
|
||||
func newResourceTranslationFromResource(res *resource.ResourceTranslation, cfg *EncoderConfig) resourceState {
|
||||
return &resourceTranslation{
|
||||
cfg: mergeConfig(cfg, res.Config()),
|
||||
|
||||
locales: res.Res,
|
||||
|
||||
refResourceTranslation: res.Res[0].Resource,
|
||||
refLocaleRes: res.RefRes,
|
||||
|
||||
refPathRes: res.RefPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *resourceTranslation) Prepare(ctx context.Context, pl *payload) (err error) {
|
||||
// Init global state
|
||||
if gResourceTranslations == nil {
|
||||
err = n.initGlobalIndex(ctx, pl.s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For now we will only allow resource specific locale resources if that resource is
|
||||
// also present.
|
||||
// Here we check if we can find it in case we're handling a resource specific rule.
|
||||
refRes := n.refLocaleRes
|
||||
if refRes.ResourceType == composeTypes.ModuleFieldResourceType {
|
||||
for _, r := range pl.state.ParentResources {
|
||||
if r.ResourceType() == composeTypes.ModuleResourceType && r.Identifiers().HasAny(n.refPathRes[1].Identifiers) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, r := range pl.state.ParentResources {
|
||||
if refRes.ResourceType == r.ResourceType() && r.Identifiers().HasAny(refRes.Identifiers) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// We couldn't find it...
|
||||
return resource.ResourceTranslationErrNotFound(refRes.Identifiers)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *resourceTranslation) Encode(ctx context.Context, pl *payload) (err error) {
|
||||
localeRes, err := n.makeResourceTranslation(pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assure correct resources
|
||||
for _, l := range n.locales {
|
||||
l.Resource = localeRes
|
||||
}
|
||||
|
||||
ll := make(types.ResourceTranslationSet, 0, len(n.locales))
|
||||
for _, l := range n.locales {
|
||||
old, ok := gResourceTranslations[l.Lang.Tag][resourceTranslationIndex(l)]
|
||||
|
||||
// not exist
|
||||
if !ok {
|
||||
l.ID = NextID()
|
||||
l.CreatedAt = *now()
|
||||
l.CreatedBy = pl.invokerID
|
||||
|
||||
ll = append(ll, l)
|
||||
continue
|
||||
}
|
||||
|
||||
// exists
|
||||
switch n.cfg.OnExisting {
|
||||
case resource.Skip,
|
||||
resource.MergeLeft:
|
||||
continue
|
||||
|
||||
case resource.MergeRight,
|
||||
resource.Default:
|
||||
old.Message = l.Message
|
||||
|
||||
ll = append(ll, old)
|
||||
}
|
||||
}
|
||||
|
||||
// Update global index for following runs
|
||||
n.updateGlobalIndex(ll)
|
||||
|
||||
return store.UpsertResourceTranslation(ctx, pl.s, ll...)
|
||||
}
|
||||
|
||||
func (n *resourceTranslation) makeResourceTranslation(pl *payload) (string, error) {
|
||||
p0ID := uint64(0)
|
||||
p1ID := uint64(0)
|
||||
p2ID := uint64(0)
|
||||
|
||||
localeRes := ""
|
||||
_ = localeRes
|
||||
|
||||
switch n.refLocaleRes.ResourceType {
|
||||
case composeTypes.NamespaceResourceType:
|
||||
p1 := resource.FindComposeNamespace(pl.state.ParentResources, n.refLocaleRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(n.refLocaleRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.ID
|
||||
|
||||
return composeTypes.NamespaceResourceTranslation(p1ID), nil
|
||||
case composeTypes.ModuleResourceType:
|
||||
p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.ID
|
||||
|
||||
p1 := resource.FindComposeModule(pl.state.ParentResources, n.refLocaleRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeModuleErrUnresolved(n.refLocaleRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.ID
|
||||
|
||||
return composeTypes.ModuleResourceTranslation(p0ID, p1ID), nil
|
||||
|
||||
case composeTypes.PageResourceType:
|
||||
p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.ID
|
||||
|
||||
p1 := resource.FindComposePage(pl.state.ParentResources, n.refLocaleRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposePageErrUnresolved(n.refLocaleRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.ID
|
||||
|
||||
return composeTypes.PageResourceTranslation(p0ID, p1ID), nil
|
||||
|
||||
case composeTypes.ModuleFieldResourceType:
|
||||
p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.ID
|
||||
|
||||
p1 := resource.FindComposeModule(pl.state.ParentResources, n.refPathRes[1].Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeModuleErrUnresolved(n.refPathRes[1].Identifiers)
|
||||
}
|
||||
p1ID = p1.ID
|
||||
|
||||
// field
|
||||
f := resource.FindComposeModuleField(p1, n.refLocaleRes.Identifiers)
|
||||
if f == nil {
|
||||
return "", resource.ComposeModuleFieldErrUnresolved(n.refLocaleRes.Identifiers)
|
||||
}
|
||||
p2ID = f.ID
|
||||
|
||||
return composeTypes.ModuleFieldResourceTranslation(p0ID, p1ID, p2ID), nil
|
||||
|
||||
default:
|
||||
// @todo if we wish to support res. trans. for external stuff, this needs to pass through.
|
||||
// this also requires some tweaks in the path ID thing.
|
||||
return "", fmt.Errorf("unsupported resource type '%s' for RBAC store encode", n.refLocaleRes.ResourceType)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *resourceTranslation) initGlobalIndex(ctx context.Context, s store.Storer) (err error) {
|
||||
gResourceTranslations = make(map[language.Tag]map[string]*types.ResourceTranslation)
|
||||
ll, _, err := store.SearchResourceTranslations(ctx, s, types.ResourceTranslationFilter{})
|
||||
if err != store.ErrNotFound && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.updateGlobalIndex(ll)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *resourceTranslation) updateGlobalIndex(ll types.ResourceTranslationSet) {
|
||||
for _, l := range ll {
|
||||
if _, ok := gResourceTranslations[l.Lang.Tag]; !ok {
|
||||
gResourceTranslations[l.Lang.Tag] = make(map[string]*types.ResourceTranslation)
|
||||
}
|
||||
gResourceTranslations[l.Lang.Tag][resourceTranslationIndex(l)] = l
|
||||
}
|
||||
}
|
||||
27
pkg/envoy/store/resource_translation_unmarshal.go
Normal file
27
pkg/envoy/store/resource_translation_unmarshal.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
func newResourceTranslation(l types.ResourceTranslationSet) (*resourceTranslation, error) {
|
||||
res := l[0].Resource
|
||||
_, ref, pp, err := resource.ParseResourceTranslation(res)
|
||||
|
||||
return &resourceTranslation{
|
||||
locales: l,
|
||||
|
||||
refResourceTranslation: res,
|
||||
refLocaleRes: ref,
|
||||
|
||||
refPathRes: pp,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (lr *resourceTranslation) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
return envoy.CollectNodes(
|
||||
resource.NewResourceTranslation(lr.locales, lr.refResourceTranslation, lr.refLocaleRes, lr.refPathRes...),
|
||||
)
|
||||
}
|
||||
@@ -25,6 +25,7 @@ type (
|
||||
resourceID map[uint64]bool
|
||||
strict bool
|
||||
}
|
||||
resourceTranslationFilter types.ResourceTranslationFilter
|
||||
|
||||
systemDecoder struct {
|
||||
resourceID []uint64
|
||||
@@ -266,6 +267,7 @@ func (d *systemDecoder) decodeSettings(ctx context.Context, s store.Storer, ff [
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rbacFilter) *auxRsp {
|
||||
mm := make([]envoy.Marshaller, 0, 100)
|
||||
if ff == nil {
|
||||
@@ -355,6 +357,81 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb
|
||||
}
|
||||
}
|
||||
|
||||
func (d *systemDecoder) decodeResourceTranslation(ctx context.Context, s store.Storer, ff []*resourceTranslationFilter) *auxRsp {
|
||||
mm := make([]envoy.Marshaller, 0, 100)
|
||||
if ff == nil {
|
||||
return &auxRsp{
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
resMap = make(map[string]*resourceTranslation)
|
||||
|
||||
nn types.ResourceTranslationSet
|
||||
fn types.ResourceTranslationFilter
|
||||
err error
|
||||
)
|
||||
|
||||
for _, f := range ff {
|
||||
aux := *f
|
||||
|
||||
if aux.Limit == 0 {
|
||||
aux.Limit = 1000
|
||||
}
|
||||
|
||||
for {
|
||||
nn, fn, err = s.SearchResourceTranslations(ctx, types.ResourceTranslationFilter(aux))
|
||||
if err != nil {
|
||||
return &auxRsp{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range nn {
|
||||
if _, ok := resMap[n.Resource]; !ok {
|
||||
resMap[n.Resource], err = newResourceTranslation(types.ResourceTranslationSet{n})
|
||||
if err != nil {
|
||||
return &auxRsp{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// parse the resource wo we can define relations/check if we can unmarshal it
|
||||
_, ref, pp, err := resource.ParseResourceTranslation(n.Resource)
|
||||
resMap[n.Resource].refLocaleRes = ref
|
||||
resMap[n.Resource].refPathRes = pp
|
||||
if err != nil {
|
||||
return &auxRsp{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resMap[n.Resource].locales = append(resMap[n.Resource].locales, n)
|
||||
}
|
||||
|
||||
if fn.NextPage != nil {
|
||||
aux.PageCursor = fn.NextPage
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, lr := range resMap {
|
||||
mm = append(mm, lr)
|
||||
}
|
||||
|
||||
return &auxRsp{
|
||||
mm: mm,
|
||||
}
|
||||
}
|
||||
|
||||
func (df *DecodeFilter) systemFromResource(rr ...string) *DecodeFilter {
|
||||
for _, r := range rr {
|
||||
if !strings.HasPrefix(r, "system") {
|
||||
@@ -377,7 +454,8 @@ func (df *DecodeFilter) systemFromResource(rr ...string) *DecodeFilter {
|
||||
})
|
||||
case "system:user":
|
||||
df = df.Users(&types.UserFilter{
|
||||
Query: id,
|
||||
Query: id,
|
||||
AllKinds: true,
|
||||
})
|
||||
case "system:template":
|
||||
df = df.Templates(&types.TemplateFilter{
|
||||
@@ -397,6 +475,8 @@ func (df *DecodeFilter) systemFromResource(rr ...string) *DecodeFilter {
|
||||
df = df.Settings(&types.SettingsFilter{})
|
||||
case "system:rbac":
|
||||
df = df.Rbac(&rbac.RuleFilter{})
|
||||
case "system:resource-translation":
|
||||
df = df.Rbac(&rbac.RuleFilter{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +542,20 @@ func (df *DecodeFilter) Rbac(f *rbac.RuleFilter) *DecodeFilter {
|
||||
return df
|
||||
}
|
||||
|
||||
// ResourceTranslation adds a new ResourceTranslationFilter
|
||||
func (df *DecodeFilter) ResourceTranslation(f *types.ResourceTranslationFilter) *DecodeFilter {
|
||||
if df.resourceTranslations == nil {
|
||||
df.resourceTranslations = make([]*resourceTranslationFilter, 0, 1)
|
||||
} else {
|
||||
// There can only be a single resourceTranslations filter
|
||||
// since it makes no sense to have multiple of
|
||||
return df
|
||||
}
|
||||
|
||||
df.resourceTranslations = append(df.resourceTranslations, (*resourceTranslationFilter)(f))
|
||||
return df
|
||||
}
|
||||
|
||||
func (df *DecodeFilter) RbacStrict(f *rbac.RuleFilter) *DecodeFilter {
|
||||
if df.rbac == nil {
|
||||
df.rbac = make([]*rbacFilter, 0, 1)
|
||||
|
||||
@@ -30,6 +30,7 @@ type (
|
||||
relNs *types.Namespace
|
||||
refNamespace string
|
||||
rbac rbacRuleSet
|
||||
locale resourceTranslationSet
|
||||
|
||||
recTpl *composeRecordTpl
|
||||
}
|
||||
@@ -43,7 +44,8 @@ type (
|
||||
|
||||
relMod *types.Module
|
||||
|
||||
rbac rbacRuleSet
|
||||
rbac rbacRuleSet
|
||||
locale resourceTranslationSet
|
||||
}
|
||||
composeModuleFieldSet []*composeModuleField
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ func (wrap *composeModule) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.locale, err = decodeLocale(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -70,15 +74,11 @@ func (wrap *composeModule) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
return y7s.NodeErr(n, "field definition must be a map")
|
||||
}
|
||||
|
||||
var (
|
||||
aux = composeModuleFieldSet{}
|
||||
)
|
||||
|
||||
if err = v.Decode(&aux); err != nil {
|
||||
if err = v.Decode(&wrap.fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wrap.res.Fields = aux.set()
|
||||
wrap.res.Fields = wrap.fields.set()
|
||||
return nil
|
||||
|
||||
case "records":
|
||||
@@ -137,10 +137,47 @@ func (wrap composeModule) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
crs = resource.NewComposeRecordTemplate(rs.Identifiers().First(), wrap.refNamespace, s.Source, s.Defaultable, mtt, s.Key...)
|
||||
}
|
||||
|
||||
// process module fields
|
||||
// - update field-defined locale
|
||||
// - collect default field locale
|
||||
fieldTranslations := make(resourceTranslationSet, 0, len(wrap.fields))
|
||||
defaultFieldTranslations := make([]resource.Interface, 0)
|
||||
for _, wrapField := range wrap.fields {
|
||||
// generic field things
|
||||
f := resource.NewComposeModuleField(wrapField.res, wrap.refNamespace, rs.Ref().Identifiers.First())
|
||||
rs.AddField(f)
|
||||
fieldTranslations = append(fieldTranslations, wrapField.locale.bindResource(f)...)
|
||||
|
||||
// default translations
|
||||
dft, err := f.EncodeTranslations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range dft {
|
||||
d.MarkDefault()
|
||||
defaultFieldTranslations = append(defaultFieldTranslations, d)
|
||||
}
|
||||
}
|
||||
|
||||
// default module translations
|
||||
var defaultModuleTranslations []resource.Interface
|
||||
dft, err := rs.EncodeTranslations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range dft {
|
||||
d.MarkDefault()
|
||||
defaultModuleTranslations = append(defaultModuleTranslations, d)
|
||||
}
|
||||
|
||||
return envoy.CollectNodes(
|
||||
rs,
|
||||
crs,
|
||||
defaultModuleTranslations,
|
||||
defaultFieldTranslations,
|
||||
wrap.rbac.bindResource(rs),
|
||||
wrap.locale.bindResource(rs),
|
||||
fieldTranslations,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -257,6 +294,10 @@ func (wrap *composeModuleField) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.locale, err = decodeLocale(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
|
||||
switch k.Value {
|
||||
case "name":
|
||||
|
||||
@@ -32,6 +32,8 @@ type (
|
||||
|
||||
// module's RBAC rules
|
||||
rbac rbacRuleSet
|
||||
|
||||
locale resourceTranslationSet
|
||||
}
|
||||
composeNamespaceSet []*composeNamespace
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ func (wrap *composeNamespace) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.locale, err = decodeLocale(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -113,12 +117,24 @@ func (wrap composeNamespace) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
nsr.SetTimestamps(wrap.ts)
|
||||
nsr.SetConfig(wrap.envoyConfig)
|
||||
|
||||
var defaultNsTranslations []resource.Interface
|
||||
dft, err := nsr.EncodeTranslations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range dft {
|
||||
t.MarkDefault()
|
||||
defaultNsTranslations = append(defaultNsTranslations, t)
|
||||
}
|
||||
|
||||
return envoy.CollectNodes(
|
||||
nsr,
|
||||
defaultNsTranslations,
|
||||
wrap.modules,
|
||||
wrap.pages,
|
||||
wrap.records,
|
||||
wrap.charts,
|
||||
wrap.rbac.bindResource(nsr),
|
||||
wrap.locale.bindResource(nsr),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type (
|
||||
applications applicationSet
|
||||
settings settingSet
|
||||
rbac rbacRuleSet
|
||||
locale resourceTranslationSet
|
||||
|
||||
cfg *EncoderConfig
|
||||
}
|
||||
@@ -39,6 +40,10 @@ func (doc *Document) UnmarshalYAML(n *yaml.Node) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if doc.locale, err = decodeLocale(n); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return y7s.EachMap(n, func(k, v *yaml.Node) error {
|
||||
switch k.Value {
|
||||
case "roles":
|
||||
@@ -134,6 +139,15 @@ func (doc *Document) MarshalYAML() (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if doc.locale != nil {
|
||||
doc.templates.configureEncoder(doc.cfg)
|
||||
|
||||
dn, err = encodeResource(dn, "locale", doc.locale, doc.cfg.MappedOutput, "lang")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if doc.rbac != nil && len(doc.rbac) > 0 {
|
||||
// rbac doesn't support map representation
|
||||
m, err := encodeNode(doc.rbac)
|
||||
@@ -177,6 +191,9 @@ func (doc *Document) Decode(ctx context.Context) ([]resource.Interface, error) {
|
||||
mm = append(mm, s)
|
||||
}
|
||||
}
|
||||
if doc.locale != nil {
|
||||
mm = append(mm, doc.locale)
|
||||
}
|
||||
if doc.rbac != nil {
|
||||
mm = append(mm, doc.rbac)
|
||||
}
|
||||
@@ -306,6 +323,14 @@ func (doc *Document) addRbacRule(r *rbacRule) {
|
||||
doc.rbac = append(doc.rbac, r)
|
||||
}
|
||||
|
||||
func (doc *Document) addResourceTranslation(l *resourceTranslation) {
|
||||
if doc.locale == nil {
|
||||
doc.locale = make(resourceTranslationSet, 0, 100)
|
||||
}
|
||||
|
||||
doc.locale = append(doc.locale, l)
|
||||
}
|
||||
|
||||
func (doc *Document) nestComposeModule(ns string, m *composeModule) error {
|
||||
if doc.compose == nil {
|
||||
doc.compose = &compose{}
|
||||
|
||||
@@ -134,6 +134,8 @@ func (ye *yamlEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
|
||||
err = f(settingFromResource(res, ye.cfg), e)
|
||||
case *resource.RbacRule:
|
||||
err = f(rbacRuleFromResource(res, ye.cfg), e)
|
||||
case *resource.ResourceTranslation:
|
||||
err = f(resourceTranslationFromResource(res, ye.cfg), e)
|
||||
|
||||
// Automation resources
|
||||
case *resource.AutomationWorkflow:
|
||||
|
||||
54
pkg/envoy/yaml/resource_translation.go
Normal file
54
pkg/envoy/yaml/resource_translation.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
resourceTranslation struct {
|
||||
// set of locales
|
||||
locales types.ResourceTranslationSet
|
||||
|
||||
// point to the locale resource
|
||||
refResourceTranslation string
|
||||
refLocaleRes *resource.Ref
|
||||
|
||||
// PathRes and PathResource slices hold parent resources we should nest the rule by
|
||||
refPathRes []*resource.Ref
|
||||
}
|
||||
|
||||
resourceTranslationSet []*resourceTranslation
|
||||
)
|
||||
|
||||
func (ll resourceTranslationSet) groupByResourceTranslation() (out resourceTranslationSet) {
|
||||
resMap := make(map[string]*resourceTranslation)
|
||||
|
||||
for _, _l := range ll {
|
||||
l := _l
|
||||
|
||||
if _, ok := resMap[l.refResourceTranslation]; !ok {
|
||||
resMap[l.refResourceTranslation] = l
|
||||
continue
|
||||
}
|
||||
|
||||
resMap[l.refResourceTranslation].locales = append(resMap[l.refResourceTranslation].locales, l.locales...)
|
||||
}
|
||||
|
||||
for _, l := range resMap {
|
||||
out = append(out, l)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func resourceTranslationFromResource(r *resource.ResourceTranslation, cfg *EncoderConfig) *resourceTranslation {
|
||||
return &resourceTranslation{
|
||||
locales: r.Res,
|
||||
|
||||
refResourceTranslation: r.Res[0].Resource,
|
||||
refLocaleRes: r.RefRes,
|
||||
|
||||
refPathRes: r.RefPath,
|
||||
}
|
||||
}
|
||||
366
pkg/envoy/yaml/resource_translation_marshal.go
Normal file
366
pkg/envoy/yaml/resource_translation_marshal.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
automationTypes "github.com/cortezaproject/corteza-server/automation/types"
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
auxLocaleMarshalRes struct {
|
||||
res string
|
||||
parts []string
|
||||
keys [][2]string
|
||||
nested []*auxLocaleMarshalRes
|
||||
}
|
||||
auxLocaleMarshalLang struct {
|
||||
lang string
|
||||
res []*auxLocaleMarshalRes
|
||||
}
|
||||
)
|
||||
|
||||
func (n *resourceTranslation) Prepare(ctx context.Context, state *envoy.ResourceState) (err error) {
|
||||
// For now we will only allow resource specific resource translations if that resource is
|
||||
// also present.
|
||||
// Here we check if we can find it in case we're handling a resource specific rule.
|
||||
//
|
||||
// @todo how can we improve this?
|
||||
res := state.Res.(*resource.ResourceTranslation)
|
||||
refRes := res.RefRes
|
||||
if refRes.ResourceType == composeTypes.ModuleFieldResourceType {
|
||||
for _, r := range state.ParentResources {
|
||||
if r.ResourceType() == composeTypes.ModuleResourceType && r.Identifiers().HasAny(res.RefPath[1].Identifiers) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, r := range state.ParentResources {
|
||||
if refRes.ResourceType == r.ResourceType() && r.Identifiers().HasAny(refRes.Identifiers) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find it...
|
||||
return resource.ResourceTranslationErrNotFound(refRes.Identifiers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resourceTranslation) Encode(ctx context.Context, doc *Document, state *envoy.ResourceState) (err error) {
|
||||
// @todo Improve locale placement
|
||||
//
|
||||
// In cases where a specific rule is created for a specific resource, nest the rule
|
||||
// under the related namespace.
|
||||
// For now all rules will be nested under a root node for simplicity sake.
|
||||
|
||||
refResource, err := r.makeResourceTranslationResource(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.refResourceTranslation = refResource
|
||||
|
||||
doc.addResourceTranslation(r)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resourceTranslation) makeResourceTranslationResource(state *envoy.ResourceState) (string, error) {
|
||||
res := state.Res.(*resource.ResourceTranslation)
|
||||
|
||||
p0ID := "*"
|
||||
p1ID := "*"
|
||||
p2ID := "*"
|
||||
|
||||
switch r.refLocaleRes.ResourceType {
|
||||
case composeTypes.NamespaceResourceType:
|
||||
if res.RefRes != nil {
|
||||
p1 := resource.FindComposeNamespace(state.ParentResources, res.RefRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.Slug
|
||||
}
|
||||
|
||||
return fmt.Sprintf(composeTypes.NamespaceResourceTranslationTpl(), composeTypes.NamespaceResourceTranslationType, p1ID), nil
|
||||
|
||||
case composeTypes.ModuleResourceType:
|
||||
if len(res.RefPath) > 0 {
|
||||
p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.Slug
|
||||
}
|
||||
|
||||
if res.RefRes != nil {
|
||||
p1 := resource.FindComposeModule(state.ParentResources, res.RefRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeModuleErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.Handle
|
||||
}
|
||||
|
||||
return fmt.Sprintf(composeTypes.ModuleResourceTranslationTpl(), composeTypes.ModuleResourceTranslationType, p0ID, p1ID), nil
|
||||
|
||||
case composeTypes.PageResourceType:
|
||||
if len(res.RefPath) > 0 {
|
||||
p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.Slug
|
||||
}
|
||||
|
||||
if res.RefRes != nil {
|
||||
p1 := resource.FindComposePage(state.ParentResources, res.RefRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposePageErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
|
||||
if p1.Handle == "" {
|
||||
p1ID = strconv.FormatUint(p1.ID, 10)
|
||||
} else {
|
||||
p1ID = p1.Handle
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf(composeTypes.PageResourceTranslationTpl(), composeTypes.PageResourceTranslationType, p0ID, p1ID), nil
|
||||
|
||||
case composeTypes.ModuleFieldResourceType:
|
||||
p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.Slug
|
||||
|
||||
p1 := resource.FindComposeModule(state.ParentResources, res.RefPath[1].Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeModuleErrUnresolved(res.RefPath[1].Identifiers)
|
||||
}
|
||||
p1ID = p1.Handle
|
||||
|
||||
// field
|
||||
f := resource.FindComposeModuleField(p1, res.RefRes.Identifiers)
|
||||
if f == nil {
|
||||
return "", resource.ComposeModuleFieldErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
p2ID = f.Name
|
||||
|
||||
return fmt.Sprintf(composeTypes.ModuleFieldResourceTranslationTpl(), composeTypes.ModuleFieldResourceTranslationType, p0ID, p1ID, p2ID), nil
|
||||
|
||||
case composeTypes.ChartResourceType:
|
||||
if len(res.RefPath) > 0 {
|
||||
p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers)
|
||||
}
|
||||
p0ID = p0.Slug
|
||||
}
|
||||
|
||||
if res.RefRes != nil {
|
||||
p1 := resource.FindComposeChart(state.ParentResources, res.RefRes.Identifiers)
|
||||
if p1 == nil {
|
||||
return "", resource.ComposeChartErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
p1ID = p1.Handle
|
||||
}
|
||||
|
||||
return fmt.Sprintf(composeTypes.ChartResourceTranslationTpl(), composeTypes.ChartResourceTranslationType, p0ID, p1ID), nil
|
||||
|
||||
case automationTypes.WorkflowResourceType:
|
||||
if res.RefRes != nil {
|
||||
p0 := resource.FindAutomationWorkflow(state.ParentResources, res.RefRes.Identifiers)
|
||||
if p0 == nil {
|
||||
return "", resource.AutomationWorkflowErrUnresolved(res.RefRes.Identifiers)
|
||||
}
|
||||
p0ID = p0.Handle
|
||||
}
|
||||
|
||||
return fmt.Sprintf(automationTypes.WorkflowResourceTranslationTpl(), automationTypes.WorkflowResourceTranslationType, p0ID), nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported resource type '%s' for locale resource YAML encode", r.refLocaleRes.ResourceType)
|
||||
}
|
||||
}
|
||||
|
||||
func (ll resourceTranslationSet) MarshalYAML() (interface{}, error) {
|
||||
|
||||
// Flow outline
|
||||
// . group by language & trans. resource
|
||||
// . sort and slice based on the resource path
|
||||
// . nest resources based on their path parameters
|
||||
// . prepare YAML nodes
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
if ll == nil || len(ll) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// . group by language & trans. resource
|
||||
//
|
||||
// We use an array+map combo to help assure consistent order when outputing.
|
||||
resourceList := make([]*auxLocaleMarshalLang, 0, 2)
|
||||
lngIndex := make(map[language.Tag]int)
|
||||
resIndex := make(map[string]int)
|
||||
for _, l := range ll {
|
||||
for _, lr := range l.locales {
|
||||
lang := lr.Lang.Tag
|
||||
|
||||
i, ok := lngIndex[lang]
|
||||
if !ok {
|
||||
i = len(resourceList)
|
||||
lngIndex[lang] = i
|
||||
resourceList = append(resourceList, &auxLocaleMarshalLang{
|
||||
lang: lang.String(),
|
||||
res: make([]*auxLocaleMarshalRes, 0, 4),
|
||||
})
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", lang.String(), l.refResourceTranslation)
|
||||
j, ok := resIndex[key]
|
||||
if !ok {
|
||||
j = len(resourceList[i].res)
|
||||
resIndex[key] = j
|
||||
resourceList[i].res = append(resourceList[i].res, &auxLocaleMarshalRes{res: l.refResourceTranslation, keys: make([][2]string, 0, 2)})
|
||||
}
|
||||
|
||||
resourceList[i].res[j].keys = append(resourceList[i].res[j].keys, [2]string{lr.K, lr.Message})
|
||||
}
|
||||
}
|
||||
|
||||
for li, l := range resourceList {
|
||||
// . sort and slice based on the resource path
|
||||
//
|
||||
// Firstly sort; have resources with less path items (slashes) at the top.
|
||||
sort.Slice(l.res, func(i, j int) bool {
|
||||
a := l.res[i]
|
||||
b := l.res[j]
|
||||
|
||||
a.parts = strings.Split(a.res, "/")
|
||||
b.parts = strings.Split(b.res, "/")
|
||||
|
||||
return len(a.parts) < len(b.parts)
|
||||
})
|
||||
//
|
||||
// Next chunk; go through the slice and group resources based on path items.
|
||||
// Since they are ordered, this is a simple traversal.
|
||||
bySize := make([][]*auxLocaleMarshalRes, 0)
|
||||
for i := len(l.res) - 1; i >= 0; i-- {
|
||||
if i == len(l.res)-1 {
|
||||
bySize = append(bySize, []*auxLocaleMarshalRes{l.res[i]})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(l.res[i].parts) != len(l.res[i+1].parts) {
|
||||
bySize = append(bySize, []*auxLocaleMarshalRes{l.res[i]})
|
||||
} else {
|
||||
bySize[len(bySize)-1] = append(bySize[len(bySize)-1], l.res[i])
|
||||
}
|
||||
}
|
||||
|
||||
// . nest resources based on their path parameters.
|
||||
//
|
||||
// Going from most specific resources to less specific resources assures
|
||||
// that we will have a correct and minimal output.
|
||||
for i := 1; i < len(bySize); i++ {
|
||||
aa := bySize[i-1]
|
||||
bb := bySize[i]
|
||||
|
||||
for _, b := range bb {
|
||||
for i, a := range aa {
|
||||
// We're not deleting rows, just setting to nil
|
||||
if a == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if isAuxLocaleParent(a, b) {
|
||||
b.nested = append(b.nested, a)
|
||||
aa[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Lastly, remove the nested hierarchy for easier YAML node construction
|
||||
tmpRes := make([]*auxLocaleMarshalRes, 0)
|
||||
for i := len(bySize) - 1; i >= 0; i-- {
|
||||
bs := bySize[i]
|
||||
for _, b := range bs {
|
||||
tmpRes = append(tmpRes, unpackAuxLocale(b)...)
|
||||
}
|
||||
}
|
||||
|
||||
l.res = tmpRes
|
||||
resourceList[li] = l
|
||||
}
|
||||
|
||||
// . prepare YAML nodes
|
||||
localeNode, _ := makeMap()
|
||||
for _, byLang := range resourceList {
|
||||
resNode, _ := makeMap()
|
||||
|
||||
for _, res := range byLang.res {
|
||||
kmNode, _ := makeMap()
|
||||
|
||||
for _, km := range res.keys {
|
||||
kmNode, err = addMap(kmNode, km[0], km[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resNode, err = addMap(resNode, res.res, kmNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
localeNode, err = addMap(localeNode, byLang.lang, resNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return localeNode, nil
|
||||
}
|
||||
|
||||
func isAuxLocaleParent(main, sub *auxLocaleMarshalRes) bool {
|
||||
for i, p := range sub.parts {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
if main.parts[i] != p {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func unpackAuxLocale(l *auxLocaleMarshalRes) []*auxLocaleMarshalRes {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*auxLocaleMarshalRes, 0)
|
||||
nn := l.nested
|
||||
l.nested = nil
|
||||
out = append(out, l)
|
||||
for _, n := range nn {
|
||||
out = append(out, unpackAuxLocale(n)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
120
pkg/envoy/yaml/resource_translation_unmarshal.go
Normal file
120
pkg/envoy/yaml/resource_translation_unmarshal.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
||||
"github.com/cortezaproject/corteza-server/pkg/y7s"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func decodeLocale(n *yaml.Node) (resourceTranslationSet, error) {
|
||||
var (
|
||||
rr = make(resourceTranslationSet, 0, 20)
|
||||
)
|
||||
|
||||
err := y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
|
||||
if k.Value != "locale" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return y7s.EachMap(v, func(k, v *yaml.Node) error {
|
||||
lang := ""
|
||||
if err = y7s.DecodeScalar(k, "language", &lang); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rr, err = rr.decodeLocale(lang, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
return rr, err
|
||||
}
|
||||
|
||||
func (rr resourceTranslationSet) decodeLocale(lang string, locRes *yaml.Node) (oo resourceTranslationSet, err error) {
|
||||
if rr == nil {
|
||||
oo = make(resourceTranslationSet, 0, 10)
|
||||
} else {
|
||||
oo = rr
|
||||
}
|
||||
|
||||
parseOps := func(km *yaml.Node, lang, resource string) error {
|
||||
return y7s.EachMap(km, func(k, msg *yaml.Node) error {
|
||||
lr := &resourceTranslation{
|
||||
locales: types.ResourceTranslationSet{{
|
||||
Lang: types.Lang{Tag: language.Make(lang)},
|
||||
Resource: resource,
|
||||
K: k.Value,
|
||||
Message: msg.Value,
|
||||
}},
|
||||
}
|
||||
|
||||
if resource != "" {
|
||||
if err = lr.setResourceTranslation(resource); err != nil {
|
||||
return fmt.Errorf("failed to decode locale resource: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
oo = append(oo, lr)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// If its a mapping node, keys represent resources
|
||||
if locRes.Content[1].Kind == yaml.MappingNode {
|
||||
return oo, y7s.EachMap(locRes, func(k, v *yaml.Node) error {
|
||||
return parseOps(v, lang, k.Value)
|
||||
})
|
||||
} else {
|
||||
err = parseOps(locRes, lang, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rr resourceTranslationSet) bindResource(resI resource.Interface) resourceTranslationSet {
|
||||
res, ref, pp := resI.(resource.LocaleInterface).ResourceTranslationParts()
|
||||
|
||||
rtr := make(resourceTranslationSet, 0, len(rr))
|
||||
for _, r := range rr {
|
||||
|
||||
r.refResourceTranslation = res
|
||||
r.refLocaleRes = ref
|
||||
r.refPathRes = pp
|
||||
rtr = append(rtr, r)
|
||||
}
|
||||
|
||||
return rtr
|
||||
}
|
||||
|
||||
func (rr resourceTranslationSet) MarshalEnvoy() ([]resource.Interface, error) {
|
||||
rr = rr.groupByResourceTranslation()
|
||||
|
||||
var nn = make([]resource.Interface, 0, len(rr))
|
||||
|
||||
for _, r := range rr {
|
||||
nn = append(nn, resource.NewResourceTranslation(r.locales, r.refResourceTranslation, r.refLocaleRes, r.refPathRes...))
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (r *resourceTranslation) setResourceTranslation(res string) error {
|
||||
_, ref, pp, err := resource.ParseResourceTranslation(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.refResourceTranslation = res
|
||||
r.refLocaleRes = ref
|
||||
r.refPathRes = pp
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user