PlantUML doc & Human Readable commentary
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources/native_tools"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/models/workflow/graph"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
@@ -133,12 +135,18 @@ func (d *Workflow) ExtractFromPlantUML(plantUML multipart.File, request *tools.A
|
||||
},
|
||||
"ComputeUnit": func() resources.ResourceInterface {
|
||||
return &resources.ComputeResource{
|
||||
|
||||
AbstractInstanciatedResource: resources.AbstractInstanciatedResource[*resources.ComputeResourceInstance]{
|
||||
Instances: []*resources.ComputeResourceInstance{},
|
||||
},
|
||||
}
|
||||
},
|
||||
// WorkflowEvent creates a NativeTool of Kind=WORKFLOW_EVENT directly,
|
||||
// without DB lookup. It has no user-defined instance.
|
||||
"WorkflowEvent": func() resources.ResourceInterface {
|
||||
return &resources.NativeTool{
|
||||
Kind: int(native_tools.WORKFLOW_EVENT),
|
||||
}
|
||||
},
|
||||
}
|
||||
graphVarName := map[string]graph.GraphItem{}
|
||||
scanner := bufio.NewScanner(plantUML)
|
||||
@@ -231,6 +239,60 @@ func (d *Workflow) generateResource(datas []resources.ResourceInterface, request
|
||||
return nil
|
||||
}
|
||||
|
||||
// setNestedKey sets a value in a nested map using dot-notation path.
|
||||
// "access.container.image" → m["access"]["container"]["image"] = value
|
||||
func setNestedKey(m map[string]any, path string, value any) {
|
||||
parts := strings.SplitN(path, ".", 2)
|
||||
if len(parts) == 1 {
|
||||
m[path] = value
|
||||
return
|
||||
}
|
||||
key, rest := parts[0], parts[1]
|
||||
if _, ok := m[key]; !ok {
|
||||
m[key] = map[string]any{}
|
||||
}
|
||||
if sub, ok := m[key].(map[string]any); ok {
|
||||
setNestedKey(sub, rest, value)
|
||||
}
|
||||
}
|
||||
|
||||
// parseHumanFriendlyAttrs converts a human-friendly comment into JSON bytes.
|
||||
// Supports:
|
||||
// - flat: "source: http://example.com, encryption: true, size: 500"
|
||||
// - nested: "access.container.image: nginx, access.container.tag: latest"
|
||||
// - raw JSON passthrough (backward-compat): '{"key": "value"}'
|
||||
//
|
||||
// Values are auto-typed: bool, float64, or string.
|
||||
// Note: the first ':' in each pair is the key/value separator,
|
||||
// so URLs like "http://..." are handled correctly.
|
||||
func parseHumanFriendlyAttrs(comment string) []byte {
|
||||
comment = strings.TrimSpace(comment)
|
||||
if strings.HasPrefix(comment, "{") {
|
||||
return []byte(comment)
|
||||
}
|
||||
m := map[string]any{}
|
||||
for _, pair := range strings.Split(comment, ",") {
|
||||
pair = strings.TrimSpace(pair)
|
||||
parts := strings.SplitN(pair, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.TrimSpace(parts[1])
|
||||
var typed any
|
||||
if b, err := strconv.ParseBool(val); err == nil {
|
||||
typed = b
|
||||
} else if n, err := strconv.ParseFloat(val, 64); err == nil {
|
||||
typed = n
|
||||
} else {
|
||||
typed = val
|
||||
}
|
||||
setNestedKey(m, key, typed)
|
||||
}
|
||||
b, _ := json.Marshal(m)
|
||||
return b
|
||||
}
|
||||
|
||||
func (d *Workflow) extractLink(line string, graphVarName map[string]graph.GraphItem, pattern string, reverse bool) error {
|
||||
splitted := strings.Split(line, pattern)
|
||||
if len(splitted) < 2 {
|
||||
@@ -255,8 +317,8 @@ func (d *Workflow) extractLink(line string, graphVarName map[string]graph.GraphI
|
||||
}
|
||||
splittedComments := strings.Split(line, "'")
|
||||
if len(splittedComments) > 1 {
|
||||
comment := strings.ReplaceAll(splittedComments[1], "'", "") // for now it's a json.
|
||||
json.Unmarshal([]byte(comment), link)
|
||||
comment := strings.ReplaceAll(splittedComments[1], "'", "")
|
||||
json.Unmarshal(parseHumanFriendlyAttrs(comment), link)
|
||||
}
|
||||
d.Graph.Links = append(d.Graph.Links, *link)
|
||||
return nil
|
||||
@@ -268,7 +330,7 @@ func (d *Workflow) extractResourcePlantUML(line string, resource resources.Resou
|
||||
return "", nil, errors.New("Can't deserialize Object, there's no func")
|
||||
}
|
||||
splittedParams := strings.Split(splittedFunc[1], ",")
|
||||
if len(splittedFunc) <= 1 {
|
||||
if len(splittedParams) <= 1 {
|
||||
return "", nil, errors.New("Can't deserialize Object, there's no params")
|
||||
}
|
||||
|
||||
@@ -281,18 +343,22 @@ func (d *Workflow) extractResourcePlantUML(line string, resource resources.Resou
|
||||
}
|
||||
resource.SetName(strings.ReplaceAll(splitted[1], "\\n", " "))
|
||||
|
||||
splittedComments := strings.Split(line, "'")
|
||||
if len(splittedComments) > 1 {
|
||||
comment := strings.ReplaceAll(splittedComments[1], "'", "") // for now it's a json.
|
||||
instance := d.getNewInstance(dataName, splitted[1], peerID)
|
||||
if instance == nil {
|
||||
return "", nil, errors.New("No instance found.")
|
||||
// Resources with instances get a default one seeded from the parent resource,
|
||||
// then overridden by any explicit comment attributes.
|
||||
// Event (NativeTool) has no instance: getNewInstance returns nil and is skipped.
|
||||
instance := d.getNewInstance(dataName, splitted[1], peerID)
|
||||
if instance != nil {
|
||||
if b, err := json.Marshal(resource); err == nil {
|
||||
json.Unmarshal(b, instance)
|
||||
}
|
||||
splittedComments := strings.Split(line, "'")
|
||||
if len(splittedComments) > 1 {
|
||||
comment := strings.ReplaceAll(splittedComments[1], "'", "")
|
||||
json.Unmarshal(parseHumanFriendlyAttrs(comment), instance)
|
||||
}
|
||||
resource.AddInstances(instance)
|
||||
|
||||
json.Unmarshal([]byte(comment), instance)
|
||||
}
|
||||
// deserializer les instances... une instance doit par défaut avoir certaines valeurs d'accès.
|
||||
|
||||
item := d.getNewGraphItem(dataName, resource)
|
||||
if item != nil {
|
||||
d.Graph.Items[item.ID] = *item
|
||||
@@ -318,15 +384,13 @@ func (d *Workflow) getNewGraphItem(dataName string, resource resources.ResourceI
|
||||
d.Processings = append(d.Processings, resource.GetID())
|
||||
d.ProcessingResources = append(d.ProcessingResources, resource.(*resources.ProcessingResource))
|
||||
graphItem.Processing = resource.(*resources.ProcessingResource)
|
||||
case "Event":
|
||||
access := resources.NewAccessor[*resources.NativeTool](tools.NATIVE_TOOL, &tools.APIRequest{
|
||||
Admin: true,
|
||||
}, func() utils.DBObject { return &resources.NativeTool{} })
|
||||
t, _, err := access.Search(nil, "WORKFLOW_EVENT", false)
|
||||
if err == nil && len(t) > 0 {
|
||||
d.NativeTool = append(d.NativeTool, t[0].GetID())
|
||||
graphItem.NativeTool = t[0].(*resources.NativeTool)
|
||||
}
|
||||
case "WorkflowEvent":
|
||||
// The resource is already a *NativeTool with Kind=WORKFLOW_EVENT set by the
|
||||
// catalog factory. We use it directly without any DB lookup.
|
||||
nt := resource.(*resources.NativeTool)
|
||||
nt.Name = native_tools.WORKFLOW_EVENT.String()
|
||||
d.NativeTool = append(d.NativeTool, nt.GetID())
|
||||
graphItem.NativeTool = nt
|
||||
case "Storage":
|
||||
d.Storages = append(d.Storages, resource.GetID())
|
||||
d.StorageResources = append(d.StorageResources, resource.(*resources.StorageResource))
|
||||
|
||||
Reference in New Issue
Block a user