Skip to content

Commit

Permalink
chore: expression for vars value (#7411)
Browse files Browse the repository at this point in the history
  • Loading branch information
leon-inf authored May 24, 2024
1 parent 70f3065 commit 6e5ac26
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 0 deletions.
18 changes: 18 additions & 0 deletions apis/apps/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,24 @@ type EnvVar struct {
//
// +optional
ValueFrom *VarSource `json:"valueFrom,omitempty"`

// A Go template expression that will be applied to the resolved value of the var.
//
// The expression will only be evaluated if the var is successfully resolved to a non-credential value.
//
// The resolved value can be accessed by its name within the expression, system vars and other user-defined
// non-credential vars can be used within the expression in the same way.
// Notice that, when accessing vars by its name, you should replace all the "-" in the name with "_", because of
// that "-" is not a valid identifier in Go.
//
// All expressions are evaluated in the order the vars are defined. If a var depends on any vars that also
// have expressions defined, be careful about the evaluation order as it may use intermediate values.
//
// The result of evaluation will be used as the final value of the var. If the expression fails to evaluate,
// the resolving of var will also be considered failed.
//
// +optional
Expression *string `json:"expression,omitempty"`
}

// VarSource represents a source for the value of an EnvVar.
Expand Down
5 changes: 5 additions & 0 deletions apis/apps/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13348,6 +13348,23 @@ spec:
description: EnvVar represents a variable present in the env of
Pod/Action or the template of config/script.
properties:
expression:
description: "A Go template expression that will be applied
to the resolved value of the var. \n The expression will only
be evaluated if the var is successfully resolved to a non-credential
value. \n The resolved value can be accessed by its name within
the expression, system vars and other user-defined non-credential
vars can be used within the expression in the same way. Notice
that, when accessing vars by its name, you should replace
all the \"-\" in the name with \"_\", because of that \"-\"
is not a valid identifier in Go. \n All expressions are evaluated
in the order the vars are defined. If a var depends on any
vars that also have expressions defined, be careful about
the evaluation order as it may use intermediate values. \n
The result of evaluation will be used as the final value of
the var. If the expression fails to evaluate, the resolving
of var will also be considered failed."
type: string
name:
description: Name of the variable. Must be a C_IDENTIFIER.
type: string
Expand Down
17 changes: 17 additions & 0 deletions deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13348,6 +13348,23 @@ spec:
description: EnvVar represents a variable present in the env of
Pod/Action or the template of config/script.
properties:
expression:
description: "A Go template expression that will be applied
to the resolved value of the var. \n The expression will only
be evaluated if the var is successfully resolved to a non-credential
value. \n The resolved value can be accessed by its name within
the expression, system vars and other user-defined non-credential
vars can be used within the expression in the same way. Notice
that, when accessing vars by its name, you should replace
all the \"-\" in the name with \"_\", because of that \"-\"
is not a valid identifier in Go. \n All expressions are evaluated
in the order the vars are defined. If a var depends on any
vars that also have expressions defined, be careful about
the evaluation order as it may use intermediate values. \n
The result of evaluation will be used as the final value of
the var. If the expression fails to evaluate, the resolving
of var will also be considered failed."
type: string
name:
description: Name of the variable. Must be a C_IDENTIFIER.
type: string
Expand Down
21 changes: 21 additions & 0 deletions docs/developer_docs/api-reference/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -11335,6 +11335,27 @@ VarSource
<p>Source for the variable&rsquo;s value. Cannot be used if value is not empty.</p>
</td>
</tr>
<tr>
<td>
<code>expression</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>A Go template expression that will be applied to the resolved value of the var.</p>
<p>The expression will only be evaluated if the var is successfully resolved to a non-credential value.</p>
<p>The resolved value can be accessed by its name within the expression, system vars and other user-defined
non-credential vars can be used within the expression in the same way.
Notice that, when accessing vars by its name, you should replace all the &ldquo;-&rdquo; in the name with &ldquo;_&rdquo;, because of
that &ldquo;-&rdquo; is not a valid identifier in Go.</p>
<p>All expressions are evaluated in the order the vars are defined. If a var depends on any vars that also
have expressions defined, be careful about the evaluation order as it may use intermediate values.</p>
<p>The result of evaluation will be used as the final value of the var. If the expression fails to evaluate,
the resolving of var will also be considered failed.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="apps.kubeblocks.io/v1alpha1.EnvVarRef">EnvVarRef
Expand Down
75 changes: 75 additions & 0 deletions pkg/controller/component/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import (
"sort"
"strconv"
"strings"
"text/template"

"github.com/Masterminds/sprig/v3"
"golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -45,6 +47,7 @@ import (

var (
varReferenceRegExp = regexp.MustCompile(`\$\(([^)]+)\)`)
varTemplate = template.New("vars").Option("missingkey=error").Funcs(sprig.TxtFuncMap())
)

func VarReferenceRegExp() *regexp.Regexp {
Expand Down Expand Up @@ -154,6 +157,9 @@ func resolveBuiltinNObjectRefVars(ctx context.Context, cli client.Reader, synthe
return nil, nil, err
}
vars = append(vars, vars1...)
if err = evaluateObjectVarsExpression(definedVars, vars2, &vars); err != nil {
return nil, nil, err
}
return vars, vars2, nil
}

Expand Down Expand Up @@ -373,6 +379,75 @@ func buildEnv4UserDefined(annotations map[string]string) ([]corev1.EnvVar, error
return vars, nil
}

func evaluateObjectVarsExpression(definedVars []appsv1alpha1.EnvVar, credentialVars []corev1.EnvVar, vars *[]corev1.EnvVar) error {
var (
isValues = make(map[string]bool)
values = make(map[string]string)
)
normalize := func(name string) string {
return strings.ReplaceAll(name, "-", "_")
}
for _, v := range [][]corev1.EnvVar{*vars, credentialVars} {
for _, vv := range v {
if vv.ValueFrom == nil {
isValues[vv.Name] = true
values[normalize(vv.Name)] = vv.Value
} else {
isValues[vv.Name] = false
}
}
}

evaluable := func(v appsv1alpha1.EnvVar) bool {
if v.Expression == nil || len(*v.Expression) == 0 {
return false
}
isValue, ok := isValues[v.Name]
// !ok is for vars that defined and resolved successfully, but have nil value.
return !ok || isValue
}

update := func(name, value string) {
if val, exist := values[normalize(name)]; exist {
if val != value {
for i := range *vars {
if (*vars)[i].Name == name {
(*vars)[i].Value = value
break
}
}
}
} else {
// TODO: insert the var to keep orders?
*vars = append(*vars, corev1.EnvVar{Name: name, Value: value})
}
values[normalize(name)] = value
}

eval := func(v appsv1alpha1.EnvVar) error {
if !evaluable(v) {
return nil
}
tpl, err := varTemplate.Parse(*v.Expression)
if err != nil {
return err
}
var buf strings.Builder
if err = tpl.Execute(&buf, values); err != nil {
return err
}
update(v.Name, buf.String())
return nil
}

for _, v := range definedVars {
if err := eval(v); err != nil {
return err
}
}
return nil
}

func resolveClusterObjectRefVars(ctx context.Context, cli client.Reader, synthesizedComp *SynthesizedComponent,
definedVars []appsv1alpha1.EnvVar) ([]corev1.EnvVar, []corev1.EnvVar, error) {
if synthesizedComp == nil {
Expand Down
Loading

0 comments on commit 6e5ac26

Please sign in to comment.