github.com/kubevela/workflow@v0.6.0/pkg/context/context.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 context 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/pkg/errors" 28 corev1 "k8s.io/api/core/v1" 29 kerrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 "github.com/kubevela/pkg/util/rand" 35 "github.com/kubevela/workflow/pkg/cue/model/value" 36 ) 37 38 const ( 39 // ConfigMapKeyVars is the key in ConfigMap Data field for containing data of variable 40 ConfigMapKeyVars = "vars" 41 // AnnotationStartTimestamp is the annotation key of the workflow start timestamp 42 AnnotationStartTimestamp = "vela.io/startTime" 43 ) 44 45 var ( 46 workflowMemoryCache sync.Map 47 ) 48 49 // WorkflowContext is workflow context. 50 type WorkflowContext struct { 51 cli client.Client 52 store *corev1.ConfigMap 53 memoryStore *sync.Map 54 vars *value.Value 55 modified bool 56 } 57 58 // GetVar get variable from workflow context. 59 func (wf *WorkflowContext) GetVar(paths ...string) (*value.Value, error) { 60 return wf.vars.LookupValue(paths...) 61 } 62 63 // SetVar set variable to workflow context. 64 func (wf *WorkflowContext) SetVar(v *value.Value, paths ...string) error { 65 str, err := v.String() 66 if err != nil { 67 return errors.WithMessage(err, "compile var") 68 } 69 if err := wf.vars.FillRaw(str, paths...); err != nil { 70 return err 71 } 72 if err := wf.vars.Error(); err != nil { 73 return err 74 } 75 wf.modified = true 76 return nil 77 } 78 79 // GetStore get store of workflow context. 80 func (wf *WorkflowContext) GetStore() *corev1.ConfigMap { 81 return wf.store 82 } 83 84 // GetMutableValue get mutable data from workflow context. 85 func (wf *WorkflowContext) GetMutableValue(paths ...string) string { 86 return wf.store.Data[strings.Join(paths, ".")] 87 } 88 89 // SetMutableValue set mutable data in workflow context config map. 90 func (wf *WorkflowContext) SetMutableValue(data string, paths ...string) { 91 wf.store.Data[strings.Join(paths, ".")] = data 92 wf.modified = true 93 } 94 95 // DeleteMutableValue delete mutable data in workflow context. 96 func (wf *WorkflowContext) DeleteMutableValue(paths ...string) { 97 key := strings.Join(paths, ".") 98 if _, ok := wf.store.Data[key]; ok { 99 delete(wf.store.Data, strings.Join(paths, ".")) 100 wf.modified = true 101 } 102 } 103 104 // IncreaseCountValueInMemory increase count in workflow context memory store. 105 func (wf *WorkflowContext) IncreaseCountValueInMemory(paths ...string) int { 106 key := strings.Join(paths, ".") 107 c, ok := wf.memoryStore.Load(key) 108 if !ok { 109 wf.memoryStore.Store(key, 0) 110 return 0 111 } 112 count, ok := c.(int) 113 if !ok { 114 wf.memoryStore.Store(key, 0) 115 return 0 116 } 117 count++ 118 wf.memoryStore.Store(key, count) 119 return count 120 } 121 122 // SetValueInMemory set data in workflow context memory store. 123 func (wf *WorkflowContext) SetValueInMemory(data interface{}, paths ...string) { 124 wf.memoryStore.Store(strings.Join(paths, "."), data) 125 } 126 127 // GetValueInMemory get data in workflow context memory store. 128 func (wf *WorkflowContext) GetValueInMemory(paths ...string) (interface{}, bool) { 129 return wf.memoryStore.Load(strings.Join(paths, ".")) 130 } 131 132 // DeleteValueInMemory delete data in workflow context memory store. 133 func (wf *WorkflowContext) DeleteValueInMemory(paths ...string) { 134 wf.memoryStore.Delete(strings.Join(paths, ".")) 135 } 136 137 // MakeParameter make 'value' with string 138 func (wf *WorkflowContext) MakeParameter(parameter string) (*value.Value, error) { 139 if parameter == "" { 140 parameter = "{}" 141 } 142 143 return wf.vars.MakeValue(parameter) 144 } 145 146 // Commit the workflow context and persist it's content. 147 func (wf *WorkflowContext) Commit() error { 148 if !wf.modified { 149 return nil 150 } 151 if err := wf.writeToStore(); err != nil { 152 return err 153 } 154 if err := wf.sync(); err != nil { 155 return errors.WithMessagef(err, "save context to configMap(%s/%s)", wf.store.Namespace, wf.store.Name) 156 } 157 return nil 158 } 159 160 func (wf *WorkflowContext) writeToStore() error { 161 varStr, err := wf.vars.String() 162 if err != nil { 163 return err 164 } 165 166 if wf.store.Data == nil { 167 wf.store.Data = make(map[string]string) 168 } 169 170 wf.store.Data[ConfigMapKeyVars] = varStr 171 return nil 172 } 173 174 func (wf *WorkflowContext) sync() error { 175 ctx := context.Background() 176 store := &corev1.ConfigMap{} 177 if EnableInMemoryContext { 178 MemStore.UpdateInMemoryContext(wf.store) 179 } else if err := wf.cli.Get(ctx, types.NamespacedName{ 180 Name: wf.store.Name, 181 Namespace: wf.store.Namespace, 182 }, store); err != nil { 183 if kerrors.IsNotFound(err) { 184 return wf.cli.Create(ctx, wf.store) 185 } 186 return err 187 } 188 return wf.cli.Patch(ctx, wf.store, client.MergeFrom(store.DeepCopy())) 189 } 190 191 // LoadFromConfigMap recover workflow context from configMap. 192 func (wf *WorkflowContext) LoadFromConfigMap(cm corev1.ConfigMap) error { 193 if wf.store == nil { 194 wf.store = &cm 195 } 196 data := cm.Data 197 198 var err error 199 wf.vars, err = value.NewValue(data[ConfigMapKeyVars], nil, "") 200 if err != nil { 201 return errors.WithMessage(err, "decode vars") 202 } 203 return nil 204 } 205 206 // StoreRef return the store reference of workflow context. 207 func (wf *WorkflowContext) StoreRef() *corev1.ObjectReference { 208 return &corev1.ObjectReference{ 209 APIVersion: wf.store.APIVersion, 210 Kind: wf.store.Kind, 211 Name: wf.store.Name, 212 Namespace: wf.store.Namespace, 213 UID: wf.store.UID, 214 } 215 } 216 217 // NewContext new workflow context without initialize data. 218 func NewContext(ctx context.Context, cli client.Client, ns, name string, owner []metav1.OwnerReference) (Context, error) { 219 wfCtx, err := newContext(ctx, cli, ns, name, owner) 220 if err != nil { 221 return nil, err 222 } 223 224 return wfCtx, nil 225 } 226 227 // CleanupMemoryStore cleans up memory store. 228 func CleanupMemoryStore(name, ns string) { 229 workflowMemoryCache.Delete(fmt.Sprintf("%s-%s", name, ns)) 230 } 231 232 func newContext(ctx context.Context, cli client.Client, ns, name string, owner []metav1.OwnerReference) (*WorkflowContext, error) { 233 store := &corev1.ConfigMap{ 234 ObjectMeta: metav1.ObjectMeta{ 235 Name: generateStoreName(name), 236 Namespace: ns, 237 OwnerReferences: owner, 238 }, 239 Data: map[string]string{ 240 ConfigMapKeyVars: "", 241 }, 242 } 243 244 kindConfigMap := reflect.TypeOf(corev1.ConfigMap{}).Name() 245 if EnableInMemoryContext { 246 MemStore.GetOrCreateInMemoryContext(store) 247 } else if err := cli.Get(ctx, client.ObjectKey{Name: store.Name, Namespace: store.Namespace}, store); err != nil { 248 if kerrors.IsNotFound(err) { 249 if err := cli.Create(ctx, store); err != nil { 250 return nil, err 251 } 252 store.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind(kindConfigMap)) 253 } else { 254 return nil, err 255 } 256 } else if !reflect.DeepEqual(store.OwnerReferences, owner) { 257 store = &corev1.ConfigMap{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Name: fmt.Sprintf("%s-%s", generateStoreName(name), rand.RandomString(5)), 260 Namespace: ns, 261 OwnerReferences: owner, 262 }, 263 Data: map[string]string{ 264 ConfigMapKeyVars: "", 265 }, 266 } 267 if err := cli.Create(ctx, store); err != nil { 268 return nil, err 269 } 270 store.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind(kindConfigMap)) 271 } 272 store.Annotations = map[string]string{ 273 AnnotationStartTimestamp: time.Now().String(), 274 } 275 memCache := getMemoryStore(fmt.Sprintf("%s-%s", name, ns)) 276 wfCtx := &WorkflowContext{ 277 cli: cli, 278 store: store, 279 memoryStore: memCache, 280 modified: true, 281 } 282 var err error 283 wfCtx.vars, err = value.NewValue("", nil, "") 284 285 return wfCtx, err 286 } 287 288 func getMemoryStore(key string) *sync.Map { 289 memCache := &sync.Map{} 290 mc, ok := workflowMemoryCache.Load(key) 291 if !ok { 292 workflowMemoryCache.Store(key, memCache) 293 } else { 294 memCache, ok = mc.(*sync.Map) 295 if !ok { 296 workflowMemoryCache.Store(key, memCache) 297 } 298 } 299 return memCache 300 } 301 302 // LoadContext load workflow context from store. 303 func LoadContext(cli client.Client, ns, name, ctxName string) (Context, error) { 304 var store corev1.ConfigMap 305 store.Name = ctxName 306 store.Namespace = ns 307 if EnableInMemoryContext { 308 MemStore.GetOrCreateInMemoryContext(&store) 309 } else if err := cli.Get(context.Background(), client.ObjectKey{ 310 Namespace: ns, 311 Name: ctxName, 312 }, &store); err != nil { 313 return nil, err 314 } 315 memCache := getMemoryStore(fmt.Sprintf("%s-%s", name, ns)) 316 ctx := &WorkflowContext{ 317 cli: cli, 318 store: &store, 319 memoryStore: memCache, 320 } 321 if err := ctx.LoadFromConfigMap(store); err != nil { 322 return nil, err 323 } 324 return ctx, nil 325 } 326 327 // generateStoreName generates the config map name of workflow context. 328 func generateStoreName(name string) string { 329 return fmt.Sprintf("workflow-%s-context", name) 330 }