mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-21 00:33:29 +00:00
282 lines
7.2 KiB
Go
282 lines
7.2 KiB
Go
package appfile
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
|
|
"cuelang.org/go/cue"
|
|
cueJson "cuelang.org/go/pkg/encoding/json"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
|
|
|
"github.com/oam-dev/kubevela/pkg/appfile/template"
|
|
mycue "github.com/oam-dev/kubevela/pkg/cue"
|
|
)
|
|
|
|
// Service defines the service spec for AppFile, it will contain all information a service realted including OAM component, traits, source to image, etc...
|
|
type Service map[string]interface{}
|
|
|
|
// DefaultWorkloadType defines the default service type if no type specified in Appfile
|
|
const DefaultWorkloadType = "webservice"
|
|
|
|
// GetType get type from AppFile
|
|
func (s Service) GetType() string {
|
|
t, ok := s["type"]
|
|
if !ok {
|
|
return DefaultWorkloadType
|
|
}
|
|
return t.(string)
|
|
}
|
|
|
|
// GetUserConfigName get user config from AppFile, it will contain config file in it.
|
|
func (s Service) GetUserConfigName() string {
|
|
t, ok := s["config"]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return t.(string)
|
|
}
|
|
|
|
// GetConfig will get OAM workload and trait information exclude inner section('build','type' and 'config')
|
|
func (s Service) GetConfig() map[string]interface{} {
|
|
config := make(map[string]interface{})
|
|
outerLoop:
|
|
for k, v := range s {
|
|
switch k {
|
|
case "build", "type", "config": // skip
|
|
continue outerLoop
|
|
}
|
|
config[k] = v
|
|
}
|
|
return config
|
|
}
|
|
|
|
// GetBuild will get source to image build info
|
|
func (s Service) GetBuild() *Build {
|
|
v, ok := s["build"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
build := &Build{}
|
|
err = json.Unmarshal(b, build)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return build
|
|
}
|
|
|
|
// RenderService render all capabilities of a service to CUE values of a Component.
|
|
// It outputs a Component which will be marshaled as standalone Component and also returned AppConfig Component section.
|
|
func (s Service) RenderService(tm template.Manager, name, ns string, cg configGetter) (*v1alpha2.ApplicationConfigurationComponent, *v1alpha2.Component, error) {
|
|
|
|
// sort out configs by workload/trait
|
|
workloadKeys := map[string]interface{}{}
|
|
traitKeys := map[string]interface{}{}
|
|
|
|
wtype := s.GetType()
|
|
|
|
for k, v := range s.GetConfig() {
|
|
if tm.IsTrait(k) {
|
|
traitKeys[k] = v
|
|
continue
|
|
}
|
|
workloadKeys[k] = v
|
|
}
|
|
|
|
// render component
|
|
component := &v1alpha2.Component{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: ns,
|
|
},
|
|
}
|
|
|
|
ctxData := map[string]interface{}{
|
|
"name": name,
|
|
}
|
|
if cn := s.GetUserConfigName(); cn != "" {
|
|
data, err := cg.GetConfigData(cn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ctxData["config"] = data
|
|
}
|
|
u, err := evalComponent(tm, wtype, ctxData, intifyValues(workloadKeys))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("eval service failed: %w", err)
|
|
}
|
|
component.Spec.Workload.Object = u
|
|
|
|
// render traits
|
|
traits := make([]v1alpha2.ComponentTrait, 0)
|
|
for traitType, traitData := range traitKeys {
|
|
ts, err := evalTraits(tm.LoadTemplate(traitType), ctxData, intifyValues(traitData))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("eval traits failed: %w", err)
|
|
}
|
|
// one capability corresponds to one trait only
|
|
if len(ts) == 1 {
|
|
ts[0].SetLabels(map[string]string{oam.TraitTypeLabel: traitType})
|
|
}
|
|
for _, t := range ts {
|
|
traits = append(traits, v1alpha2.ComponentTrait{
|
|
Trait: runtime.RawExtension{
|
|
Object: t,
|
|
}},
|
|
)
|
|
}
|
|
}
|
|
|
|
acComp := &v1alpha2.ApplicationConfigurationComponent{
|
|
ComponentName: component.Name,
|
|
Traits: traits,
|
|
}
|
|
return acComp, component, nil
|
|
}
|
|
|
|
// GetServices will get all services defined in AppFile
|
|
func (af *AppFile) GetServices() map[string]Service {
|
|
return af.Services
|
|
}
|
|
|
|
func isIntegral(val float64) bool {
|
|
return val == float64(int(val))
|
|
}
|
|
|
|
// JSON marshalling of user values will put integer into float,
|
|
// we have to change it back so that CUE check will succeed.
|
|
func intifyValues(raw interface{}) interface{} {
|
|
switch v := raw.(type) {
|
|
case map[string]interface{}:
|
|
return intifyMap(v)
|
|
case []interface{}:
|
|
return intifyList(v)
|
|
case float64:
|
|
if isIntegral(v) {
|
|
return int(v)
|
|
}
|
|
return v
|
|
default:
|
|
return raw
|
|
}
|
|
}
|
|
|
|
func intifyList(l []interface{}) interface{} {
|
|
l2 := make([]interface{}, 0, len(l))
|
|
for _, v := range l {
|
|
l2 = append(l2, intifyValues(v))
|
|
}
|
|
return l2
|
|
}
|
|
|
|
func intifyMap(m map[string]interface{}) interface{} {
|
|
m2 := make(map[string]interface{}, len(m))
|
|
for k, v := range m {
|
|
m2[k] = intifyValues(v)
|
|
}
|
|
return m2
|
|
}
|
|
|
|
func getValueStruct(raw string, ctxValues, userValues interface{}) (*cue.Struct, error) {
|
|
r := &cue.Runtime{}
|
|
template, err := r.Compile("", raw+mycue.BaseTemplate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("compile CUE template failed: %w", err)
|
|
}
|
|
// fill values
|
|
rootValue := template.Value()
|
|
rootValue = rootValue.Fill(ctxValues, "context")
|
|
rootValue = rootValue.Fill(intifyValues(userValues), "parameter")
|
|
appValue, err := rootValue.Eval().Struct()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("eval CUE template failed: %w", err)
|
|
}
|
|
return appValue, nil
|
|
}
|
|
|
|
func renderOneOutput(appValue *cue.Struct) (*unstructured.Unstructured, error) {
|
|
outputField, err := appValue.FieldByName("output", true)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FieldByName('output'): %w", err)
|
|
}
|
|
|
|
final := outputField.Value
|
|
data, err := cueJson.Marshal(final)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal final value failed: %w", err)
|
|
}
|
|
obj := make(map[string]interface{})
|
|
if err = json.Unmarshal([]byte(data), &obj); err != nil {
|
|
return nil, err
|
|
}
|
|
return &unstructured.Unstructured{
|
|
Object: obj,
|
|
}, nil
|
|
}
|
|
|
|
func renderAllOutputs(field cue.FieldInfo) ([]*unstructured.Unstructured, error) {
|
|
iter, err := field.Value.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
us := []*unstructured.Unstructured{}
|
|
for iter.Next() {
|
|
final := iter.Value()
|
|
data, err := cueJson.Marshal(final)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal final value err %w", err)
|
|
}
|
|
// need to unmarshal it to a map to get rid of the outer spec name
|
|
obj := make(map[string]interface{})
|
|
if err = json.Unmarshal([]byte(data), &obj); err != nil {
|
|
return nil, err
|
|
}
|
|
u := &unstructured.Unstructured{Object: obj}
|
|
us = append(us, u)
|
|
}
|
|
return us, nil
|
|
}
|
|
|
|
func evalComponent(tm template.Manager, wtype string, ctxValues, userValues interface{}) (*unstructured.Unstructured, error) {
|
|
workloadCueTemplate := tm.LoadTemplate(wtype)
|
|
if workloadCueTemplate == "" {
|
|
return nil, fmt.Errorf("no template found in capability %s", wtype)
|
|
}
|
|
appValue, err := getValueStruct(workloadCueTemplate, ctxValues, userValues)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return renderOneOutput(appValue)
|
|
}
|
|
|
|
func evalTraits(raw string, ctxValues, userValues interface{}) ([]*unstructured.Unstructured, error) {
|
|
appValue, err := getValueStruct(raw, ctxValues, userValues)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = appValue.FieldByName("output", true)
|
|
if err != nil {
|
|
outputField, err := appValue.FieldByName("outputs", true)
|
|
if err != nil {
|
|
return nil, errors.New("both output and outputs fields not found")
|
|
}
|
|
return renderAllOutputs(outputField)
|
|
}
|
|
u, err := renderOneOutput(appValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []*unstructured.Unstructured{u}, nil
|
|
}
|