mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
* Refactor: sort map keys before rendering in CUE generation functions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Feat: add tests for alphabetical ordering of labels and outputs in CUE generation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Feat: add tests for alphabetical ordering of computed fields and output groups in CUE generation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> --------- Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
349 lines
11 KiB
Go
349 lines
11 KiB
Go
/*
|
|
Copyright 2025 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package defkit
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// CUERenderer is implemented by builder types that can render themselves to CUE.
|
|
// The renderValue function is provided by the CUE generator to render nested Value types.
|
|
type CUERenderer interface {
|
|
RenderCUE(renderValue func(Value) string) string
|
|
}
|
|
|
|
// CUEConditionRenderer extends CUERenderer with condition rendering support.
|
|
// Builders that need to render Condition values should implement this interface.
|
|
// The CUE generator will prefer this over CUERenderer when available.
|
|
type CUEConditionRenderer interface {
|
|
RenderCUEWithCondition(renderValue func(Value) string, renderCondition func(Condition) string) string
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// KubeRead builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// KubeReadBuilder builds a kube.#Read operation fluently.
|
|
type KubeReadBuilder struct {
|
|
apiVersion string
|
|
kind string
|
|
name Value
|
|
namespace Value
|
|
cluster Value
|
|
nsCond Condition // for NamespaceIf
|
|
}
|
|
|
|
func (b *KubeReadBuilder) expr() {}
|
|
func (b *KubeReadBuilder) value() {}
|
|
func (b *KubeReadBuilder) condition() {}
|
|
|
|
// KubeRead creates a new kube.#Read builder for the given apiVersion and kind.
|
|
func KubeRead(apiVersion, kind string) *KubeReadBuilder {
|
|
return &KubeReadBuilder{
|
|
apiVersion: apiVersion,
|
|
kind: kind,
|
|
}
|
|
}
|
|
|
|
// Name sets the metadata.name value.
|
|
func (b *KubeReadBuilder) Name(v Value) *KubeReadBuilder {
|
|
b.name = v
|
|
return b
|
|
}
|
|
|
|
// Namespace sets the metadata.namespace value.
|
|
func (b *KubeReadBuilder) Namespace(v Value) *KubeReadBuilder {
|
|
b.namespace = v
|
|
return b
|
|
}
|
|
|
|
// NamespaceIf sets the metadata.namespace conditionally.
|
|
func (b *KubeReadBuilder) NamespaceIf(cond Condition, v Value) *KubeReadBuilder {
|
|
b.nsCond = cond
|
|
b.namespace = v
|
|
return b
|
|
}
|
|
|
|
// Cluster sets the optional cluster parameter for multi-cluster reads.
|
|
func (b *KubeReadBuilder) Cluster(v Value) *KubeReadBuilder {
|
|
b.cluster = v
|
|
return b
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *KubeReadBuilder) RenderCUE(rv func(Value) string) string {
|
|
return b.RenderCUEWithCondition(rv, nil)
|
|
}
|
|
|
|
// RenderCUEWithCondition renders the builder to a CUE string with condition support.
|
|
func (b *KubeReadBuilder) RenderCUEWithCondition(rv func(Value) string, rc func(Condition) string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("kube.#Read & {\n")
|
|
|
|
// cluster param sits alongside value in $params
|
|
if b.cluster != nil {
|
|
sb.WriteString("\t$params: {\n")
|
|
sb.WriteString(fmt.Sprintf("\t\tcluster: %s\n", rv(b.cluster)))
|
|
sb.WriteString("\t\tvalue: {\n")
|
|
b.writeValueBody(&sb, rv, rc, "\t\t\t")
|
|
sb.WriteString("\t\t}\n")
|
|
sb.WriteString("\t}\n")
|
|
} else {
|
|
sb.WriteString("\t$params: value: {\n")
|
|
b.writeValueBody(&sb, rv, rc, "\t\t")
|
|
sb.WriteString("\t}\n")
|
|
}
|
|
|
|
sb.WriteString("}")
|
|
return sb.String()
|
|
}
|
|
|
|
func (b *KubeReadBuilder) writeValueBody(sb *strings.Builder, rv func(Value) string, rc func(Condition) string, indent string) {
|
|
sb.WriteString(fmt.Sprintf("%sapiVersion: %q\n", indent, b.apiVersion))
|
|
sb.WriteString(fmt.Sprintf("%skind: %q\n", indent, b.kind))
|
|
sb.WriteString(fmt.Sprintf("%smetadata: {\n", indent))
|
|
if b.name != nil {
|
|
sb.WriteString(fmt.Sprintf("%s\tname: %s\n", indent, rv(b.name)))
|
|
}
|
|
if b.namespace != nil {
|
|
if b.nsCond != nil && rc != nil {
|
|
condStr := rc(b.nsCond)
|
|
sb.WriteString(fmt.Sprintf("%s\tif %s {\n", indent, condStr))
|
|
sb.WriteString(fmt.Sprintf("%s\t\tnamespace: %s\n", indent, rv(b.namespace)))
|
|
sb.WriteString(fmt.Sprintf("%s\t}\n", indent))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s\tnamespace: %s\n", indent, rv(b.namespace)))
|
|
}
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// KubeApply builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// KubeApplyBuilder builds a kube.#Apply operation fluently.
|
|
type KubeApplyBuilder struct {
|
|
objectValue Value
|
|
cluster Value
|
|
}
|
|
|
|
func (b *KubeApplyBuilder) expr() {}
|
|
func (b *KubeApplyBuilder) value() {}
|
|
func (b *KubeApplyBuilder) condition() {}
|
|
|
|
// KubeApply creates a new kube.#Apply builder with the given object value.
|
|
func KubeApply(objectValue Value) *KubeApplyBuilder {
|
|
return &KubeApplyBuilder{objectValue: objectValue}
|
|
}
|
|
|
|
// Cluster sets the optional cluster parameter.
|
|
func (b *KubeApplyBuilder) Cluster(v Value) *KubeApplyBuilder {
|
|
b.cluster = v
|
|
return b
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *KubeApplyBuilder) RenderCUE(rv func(Value) string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("kube.#Apply & {\n")
|
|
sb.WriteString("\t$params: {\n")
|
|
sb.WriteString(fmt.Sprintf("\t\tvalue: %s\n", rv(b.objectValue)))
|
|
if b.cluster != nil {
|
|
sb.WriteString(fmt.Sprintf("\t\tcluster: %s\n", rv(b.cluster)))
|
|
}
|
|
sb.WriteString("\t}\n")
|
|
sb.WriteString("}")
|
|
return sb.String()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HTTPPost builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// HTTPPostBuilder builds an http.#HTTPDo POST operation fluently.
|
|
type HTTPPostBuilder struct {
|
|
url Value
|
|
body Value
|
|
headers map[string]string
|
|
}
|
|
|
|
func (b *HTTPPostBuilder) expr() {}
|
|
func (b *HTTPPostBuilder) value() {}
|
|
func (b *HTTPPostBuilder) condition() {}
|
|
|
|
// HTTPPost creates a new http.#HTTPDo builder for a POST request.
|
|
func HTTPPost(url Value) *HTTPPostBuilder {
|
|
return &HTTPPostBuilder{
|
|
url: url,
|
|
headers: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// Body sets the request body.
|
|
func (b *HTTPPostBuilder) Body(v Value) *HTTPPostBuilder {
|
|
b.body = v
|
|
return b
|
|
}
|
|
|
|
// Header adds a request header.
|
|
func (b *HTTPPostBuilder) Header(key, value string) *HTTPPostBuilder {
|
|
b.headers[key] = value
|
|
return b
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *HTTPPostBuilder) RenderCUE(rv func(Value) string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("http.#HTTPDo & {\n")
|
|
sb.WriteString("\t$params: {\n")
|
|
sb.WriteString("\t\tmethod: \"POST\"\n")
|
|
sb.WriteString(fmt.Sprintf("\t\turl: %s\n", rv(b.url)))
|
|
sb.WriteString("\t\trequest: {\n")
|
|
if b.body != nil {
|
|
sb.WriteString(fmt.Sprintf("\t\t\tbody: %s\n", rv(b.body)))
|
|
}
|
|
for _, k := range sortedKeys(b.headers) {
|
|
sb.WriteString(fmt.Sprintf("\t\t\theader: %q: %q\n", k, b.headers[k]))
|
|
}
|
|
sb.WriteString("\t\t}\n")
|
|
sb.WriteString("\t}\n")
|
|
sb.WriteString("}")
|
|
return sb.String()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ConvertString builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ConvertStringBuilder builds a util.#ConvertString operation fluently.
|
|
type ConvertStringBuilder struct {
|
|
input Value
|
|
}
|
|
|
|
func (b *ConvertStringBuilder) expr() {}
|
|
func (b *ConvertStringBuilder) value() {}
|
|
func (b *ConvertStringBuilder) condition() {}
|
|
|
|
// ConvertString creates a new util.#ConvertString builder.
|
|
func ConvertString(input Value) *ConvertStringBuilder {
|
|
return &ConvertStringBuilder{input: input}
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *ConvertStringBuilder) RenderCUE(rv func(Value) string) string {
|
|
return fmt.Sprintf("util.#ConvertString & {\n\t$params: bt: %s\n}", rv(b.input))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// WaitUntil builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// WaitUntilBuilder builds a builtin.#ConditionalWait operation fluently.
|
|
type WaitUntilBuilder struct {
|
|
continueExpr Value
|
|
guards []Value
|
|
messageCond Condition
|
|
messageVal Value
|
|
}
|
|
|
|
func (b *WaitUntilBuilder) expr() {}
|
|
func (b *WaitUntilBuilder) value() {}
|
|
func (b *WaitUntilBuilder) condition() {}
|
|
|
|
// WaitUntil creates a new builtin.#ConditionalWait builder.
|
|
func WaitUntil(continueExpr Value) *WaitUntilBuilder {
|
|
return &WaitUntilBuilder{continueExpr: continueExpr}
|
|
}
|
|
|
|
// Guard adds guard conditions that must be true (not _|_) before the continue
|
|
// check is evaluated. Multiple guards are nested as chained if statements.
|
|
func (b *WaitUntilBuilder) Guard(guards ...Value) *WaitUntilBuilder {
|
|
b.guards = append(b.guards, guards...)
|
|
return b
|
|
}
|
|
|
|
// MessageIf conditionally sets the message parameter.
|
|
func (b *WaitUntilBuilder) MessageIf(cond Condition, val Value) *WaitUntilBuilder {
|
|
b.messageCond = cond
|
|
b.messageVal = val
|
|
return b
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *WaitUntilBuilder) RenderCUE(rv func(Value) string) string {
|
|
return b.RenderCUEWithCondition(rv, nil)
|
|
}
|
|
|
|
// RenderCUEWithCondition renders the builder to a CUE string with condition support.
|
|
func (b *WaitUntilBuilder) RenderCUEWithCondition(rv func(Value) string, rc func(Condition) string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("builtin.#ConditionalWait & {\n")
|
|
|
|
if len(b.guards) > 0 {
|
|
// Build chained guard: if g1 if g2 { ... }
|
|
var guardParts []string
|
|
for _, g := range b.guards {
|
|
guardParts = append(guardParts, fmt.Sprintf("if %s != _|_", rv(g)))
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t%s {\n", strings.Join(guardParts, " ")))
|
|
sb.WriteString(fmt.Sprintf("\t\t$params: continue: %s\n", rv(b.continueExpr)))
|
|
sb.WriteString("\t}\n")
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("\t$params: continue: %s\n", rv(b.continueExpr)))
|
|
}
|
|
|
|
if b.messageCond != nil && b.messageVal != nil {
|
|
if rc != nil {
|
|
condStr := rc(b.messageCond)
|
|
sb.WriteString(fmt.Sprintf("\tif %s {\n", condStr))
|
|
} else {
|
|
// Fallback when condition renderer is not available
|
|
sb.WriteString(fmt.Sprintf("\tif %s != _|_ {\n", rv(b.messageVal)))
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t\t$params: message: %s\n", rv(b.messageVal)))
|
|
sb.WriteString("\t}\n")
|
|
}
|
|
|
|
sb.WriteString("}")
|
|
return sb.String()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fail builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// FailBuilder builds a builtin.#Fail operation fluently.
|
|
type FailBuilder struct {
|
|
message Value
|
|
}
|
|
|
|
func (b *FailBuilder) expr() {}
|
|
func (b *FailBuilder) value() {}
|
|
func (b *FailBuilder) condition() {}
|
|
|
|
// Fail creates a new builtin.#Fail builder with the given message.
|
|
func Fail(message Value) *FailBuilder {
|
|
return &FailBuilder{message: message}
|
|
}
|
|
|
|
// RenderCUE renders the builder to a CUE string.
|
|
func (b *FailBuilder) RenderCUE(rv func(Value) string) string {
|
|
return fmt.Sprintf("builtin.#Fail & {\n\t$params: message: %s\n}", rv(b.message))
|
|
}
|