github.com/kubevela/workflow@v0.6.0/pkg/cue/process/handle.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package process
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"reflect"
    24  	"strings"
    25  	"unicode"
    26  
    27  	"github.com/pkg/errors"
    28  	"k8s.io/klog/v2"
    29  
    30  	"github.com/kubevela/workflow/pkg/cue/model"
    31  )
    32  
    33  // Context defines Rendering Context Interface
    34  type Context interface {
    35  	SetBase(base model.Instance) error
    36  	AppendAuxiliaries(auxiliaries ...Auxiliary) error
    37  	Output() (model.Instance, []Auxiliary)
    38  	BaseContextFile() (string, error)
    39  	BaseContextLabels() map[string]string
    40  	SetParameters(params map[string]interface{})
    41  	PushData(key string, data interface{})
    42  	RemoveData(key string)
    43  	GetData(key string) interface{}
    44  	GetCtx() context.Context
    45  	SetCtx(context.Context)
    46  }
    47  
    48  // Auxiliary are objects rendered by definition template.
    49  // the format for auxiliary resource is always: `outputs.<resourceName>`, it can be auxiliary workload or trait
    50  type Auxiliary struct {
    51  	Ins model.Instance
    52  	// Type will be used to mark definition label for OAM runtime to get the CRD
    53  	// It's now required for trait and main workload object. Extra workload CR object will not have the type.
    54  	Type string
    55  
    56  	// Workload or trait with multiple `outputs` will have a name, if name is empty, than it's the main of this type.
    57  	Name string
    58  }
    59  
    60  type templateContext struct {
    61  	base        model.Instance
    62  	auxiliaries []Auxiliary
    63  
    64  	baseHooks      []BaseHook
    65  	auxiliaryHooks []AuxiliaryHook
    66  
    67  	customData map[string]interface{}
    68  	data       map[string]interface{}
    69  
    70  	ctx context.Context
    71  }
    72  
    73  // RequiredSecrets is used to store all secret names which are generated by cloud resource components and required by current component
    74  type RequiredSecrets struct {
    75  	Namespace   string
    76  	Name        string
    77  	ContextName string
    78  	Data        map[string]interface{}
    79  }
    80  
    81  // ContextData is the core data of process context
    82  type ContextData struct {
    83  	Name           string
    84  	Namespace      string
    85  	StepName       string
    86  	WorkflowName   string
    87  	PublishVersion string
    88  
    89  	Ctx            context.Context
    90  	CustomData     map[string]interface{}
    91  	Data           map[string]interface{}
    92  	BaseHooks      []BaseHook
    93  	AuxiliaryHooks []AuxiliaryHook
    94  }
    95  
    96  // NewContext create render templateContext
    97  func NewContext(data ContextData) Context {
    98  	ctx := &templateContext{
    99  		ctx:            data.Ctx,
   100  		customData:     data.CustomData,
   101  		data:           data.Data,
   102  		baseHooks:      data.BaseHooks,
   103  		auxiliaryHooks: data.AuxiliaryHooks,
   104  		auxiliaries:    []Auxiliary{},
   105  	}
   106  	ctx.PushData(model.ContextName, data.Name)
   107  	ctx.PushData(model.ContextNamespace, data.Namespace)
   108  	ctx.PushData(model.ContextWorkflowName, data.WorkflowName)
   109  	ctx.PushData(model.ContextPublishVersion, data.PublishVersion)
   110  	return ctx
   111  }
   112  
   113  // SetParameters sets templateContext parameters
   114  func (ctx *templateContext) SetParameters(params map[string]interface{}) {
   115  	ctx.PushData(model.ParameterFieldName, params)
   116  }
   117  
   118  // SetBase set templateContext base model
   119  func (ctx *templateContext) SetBase(base model.Instance) error {
   120  	for _, hook := range ctx.baseHooks {
   121  		if err := hook.Exec(ctx, base); err != nil {
   122  			return errors.Wrap(err, "cannot set base into context")
   123  		}
   124  	}
   125  	ctx.base = base
   126  	return nil
   127  }
   128  
   129  // AppendAuxiliaries add Assist model to templateContext
   130  func (ctx *templateContext) AppendAuxiliaries(auxiliaries ...Auxiliary) error {
   131  	for _, hook := range ctx.auxiliaryHooks {
   132  		if err := hook.Exec(ctx, auxiliaries); err != nil {
   133  			return errors.Wrap(err, "cannot append auxiliaries into context")
   134  		}
   135  	}
   136  	ctx.auxiliaries = append(ctx.auxiliaries, auxiliaries...)
   137  	return nil
   138  }
   139  
   140  // BaseContextFile return cue format string of templateContext
   141  func (ctx *templateContext) BaseContextFile() (string, error) {
   142  	var buff string
   143  
   144  	if ctx.base != nil {
   145  		base, err := ctx.base.Value().MarshalJSON()
   146  		if err != nil {
   147  			return "", err
   148  		}
   149  		var b any
   150  		if err := json.Unmarshal(base, &b); err != nil {
   151  			return "", err
   152  		}
   153  		ctx.PushData(model.OutputFieldName, b)
   154  	}
   155  
   156  	if len(ctx.auxiliaries) > 0 {
   157  		auxLines := make(map[string]any)
   158  		for _, auxiliary := range ctx.auxiliaries {
   159  			aux, err := auxiliary.Ins.Value().MarshalJSON()
   160  			if err != nil {
   161  				return "", err
   162  			}
   163  			var a any
   164  			if err := json.Unmarshal(aux, &a); err != nil {
   165  				return "", err
   166  			}
   167  			auxLines[auxiliary.Name] = a
   168  		}
   169  		if len(auxLines) > 0 {
   170  			ctx.PushData(model.OutputsFieldName, auxLines)
   171  		}
   172  	}
   173  
   174  	for k, v := range ctx.customData {
   175  		if exist := ctx.GetData(k); exist != nil && !reflect.DeepEqual(exist, v) {
   176  			klog.Warningf("Built-in value [%s: %s] in context will be overridden", k, exist)
   177  		}
   178  		ctx.PushData(k, v)
   179  	}
   180  
   181  	if ctx.data != nil {
   182  		d, err := json.Marshal(ctx.data)
   183  		if err != nil {
   184  			return "", err
   185  		}
   186  		buff += fmt.Sprintf("\n %s", structMarshal(string(d)))
   187  	}
   188  
   189  	return fmt.Sprintf("context: %s", structMarshal(buff)), nil
   190  }
   191  
   192  func (ctx *templateContext) BaseContextLabels() map[string]string {
   193  	return map[string]string{
   194  		model.ContextName: fmt.Sprint(ctx.GetData(model.ContextName)),
   195  	}
   196  }
   197  
   198  // Output return model and auxiliaries of templateContext
   199  func (ctx *templateContext) Output() (model.Instance, []Auxiliary) {
   200  	return ctx.base, ctx.auxiliaries
   201  }
   202  
   203  // InsertSecrets will add cloud resource secret stuff to context
   204  func (ctx *templateContext) InsertSecrets(outputSecretName string, requiredSecrets []RequiredSecrets) {
   205  	if outputSecretName != "" {
   206  		ctx.PushData(model.OutputSecretName, outputSecretName)
   207  	}
   208  	for _, s := range requiredSecrets {
   209  		ctx.PushData(s.ContextName, s.Data)
   210  	}
   211  }
   212  
   213  // PushData appends arbitrary extension data to context
   214  func (ctx *templateContext) PushData(key string, data interface{}) {
   215  	if ctx.data == nil {
   216  		ctx.data = map[string]interface{}{key: data}
   217  		return
   218  	}
   219  	ctx.data[key] = data
   220  }
   221  
   222  func (ctx *templateContext) RemoveData(key string) {
   223  	delete(ctx.data, key)
   224  }
   225  
   226  // GetData get data from context
   227  func (ctx *templateContext) GetData(key string) interface{} {
   228  	return ctx.data[key]
   229  }
   230  
   231  func (ctx *templateContext) GetCtx() context.Context {
   232  	if ctx.ctx != nil {
   233  		return ctx.ctx
   234  	}
   235  	return context.TODO()
   236  }
   237  
   238  func (ctx *templateContext) SetCtx(newContext context.Context) {
   239  	ctx.ctx = newContext
   240  }
   241  
   242  func structMarshal(v string) string {
   243  	skip := false
   244  	v = strings.TrimFunc(v, func(r rune) bool {
   245  		if !skip {
   246  			if unicode.IsSpace(r) {
   247  				return true
   248  			}
   249  			skip = true
   250  
   251  		}
   252  		return false
   253  	})
   254  
   255  	if strings.HasPrefix(v, "{") {
   256  		return v
   257  	}
   258  	return fmt.Sprintf("{%s}", v)
   259  }