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 }