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  }