github.com/kubevela/workflow@v0.6.0/pkg/providers/kube/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 kube
    18  
    19  import (
    20  	"context"
    21  
    22  	"cuelang.org/go/cue"
    23  	"cuelang.org/go/cue/cuecontext"
    24  	"k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	ktypes "k8s.io/apimachinery/pkg/types"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	monitorContext "github.com/kubevela/pkg/monitor/context"
    31  	"github.com/kubevela/pkg/multicluster"
    32  	"github.com/kubevela/pkg/util/k8s"
    33  	"github.com/kubevela/pkg/util/k8s/patch"
    34  
    35  	wfContext "github.com/kubevela/workflow/pkg/context"
    36  	velacue "github.com/kubevela/workflow/pkg/cue"
    37  	"github.com/kubevela/workflow/pkg/cue/model"
    38  	"github.com/kubevela/workflow/pkg/cue/model/value"
    39  	"github.com/kubevela/workflow/pkg/types"
    40  )
    41  
    42  const (
    43  	// ProviderName is provider name for install.
    44  	ProviderName = "kube"
    45  	// AnnoWorkflowLastAppliedConfig is the annotation for last applied config
    46  	AnnoWorkflowLastAppliedConfig = "workflow.oam.dev/last-applied-configuration"
    47  	// AnnoWorkflowLastAppliedTime is annotation for last applied time
    48  	AnnoWorkflowLastAppliedTime = "workflow.oam.dev/last-applied-time"
    49  )
    50  
    51  // Dispatcher is a client for apply resources.
    52  type Dispatcher func(ctx context.Context, cluster, owner string, manifests ...*unstructured.Unstructured) error
    53  
    54  // Deleter is a client for delete resources.
    55  type Deleter func(ctx context.Context, cluster, owner string, manifest *unstructured.Unstructured) error
    56  
    57  // Handlers handles resources.
    58  type Handlers struct {
    59  	Apply  Dispatcher
    60  	Delete Deleter
    61  }
    62  
    63  type filters struct {
    64  	Namespace      string            `json:"namespace"`
    65  	MatchingLabels map[string]string `json:"matchingLabels"`
    66  }
    67  
    68  type provider struct {
    69  	labels   map[string]string
    70  	handlers Handlers
    71  	cli      client.Client
    72  }
    73  
    74  const (
    75  	// WorkflowResourceCreator is the creator name of workflow resource
    76  	WorkflowResourceCreator string = "workflow"
    77  )
    78  
    79  func handleContext(ctx context.Context, cluster string) context.Context {
    80  	return multicluster.WithCluster(ctx, cluster)
    81  }
    82  
    83  type dispatcher struct {
    84  	cli client.Client
    85  }
    86  
    87  func (d *dispatcher) apply(ctx context.Context, cluster, owner string, workloads ...*unstructured.Unstructured) error {
    88  	for _, workload := range workloads {
    89  		existing := new(unstructured.Unstructured)
    90  		existing.GetObjectKind().SetGroupVersionKind(workload.GetObjectKind().GroupVersionKind())
    91  		if err := d.cli.Get(ctx, ktypes.NamespacedName{
    92  			Namespace: workload.GetNamespace(),
    93  			Name:      workload.GetName(),
    94  		}, existing); err != nil {
    95  			if errors.IsNotFound(err) {
    96  				// TODO: make the annotation optional
    97  				b, err := workload.MarshalJSON()
    98  				if err != nil {
    99  					return err
   100  				}
   101  				if err := k8s.AddAnnotation(workload, AnnoWorkflowLastAppliedConfig, string(b)); err != nil {
   102  					return err
   103  				}
   104  				if err := d.cli.Create(ctx, workload); err != nil {
   105  					return err
   106  				}
   107  			} else {
   108  				return err
   109  			}
   110  		} else {
   111  			patcher, err := patch.ThreeWayMergePatch(existing, workload, &patch.PatchAction{
   112  				UpdateAnno:            true,
   113  				AnnoLastAppliedConfig: AnnoWorkflowLastAppliedConfig,
   114  				AnnoLastAppliedTime:   AnnoWorkflowLastAppliedTime,
   115  			})
   116  			if err != nil {
   117  				return err
   118  			}
   119  			if err := d.cli.Patch(ctx, workload, patcher); err != nil {
   120  				return err
   121  			}
   122  		}
   123  	}
   124  	return nil
   125  }
   126  
   127  func (d *dispatcher) delete(ctx context.Context, cluster, owner string, manifest *unstructured.Unstructured) error {
   128  	return d.cli.Delete(ctx, manifest)
   129  }
   130  
   131  // Patch patch CR in cluster.
   132  func (h *provider) Patch(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   133  	val, err := v.LookupValue("value")
   134  	if err != nil {
   135  		return err
   136  	}
   137  	obj := new(unstructured.Unstructured)
   138  	if err := val.UnmarshalTo(obj); err != nil {
   139  		return err
   140  	}
   141  	key := client.ObjectKeyFromObject(obj)
   142  	if key.Namespace == "" {
   143  		key.Namespace = "default"
   144  	}
   145  	cluster, err := v.GetString("cluster")
   146  	if err != nil {
   147  		return err
   148  	}
   149  	multiCtx := handleContext(ctx, cluster)
   150  	if err := h.cli.Get(multiCtx, key, obj); err != nil {
   151  		return err
   152  	}
   153  	baseVal := cuecontext.New().CompileString("").FillPath(cue.ParsePath(""), obj)
   154  	patcher, err := v.LookupValue("patch")
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	base, err := model.NewBase(baseVal)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if err := base.Unify(patcher.CueValue()); err != nil {
   164  		return err
   165  	}
   166  	workload, err := base.Unstructured()
   167  	if err != nil {
   168  		return err
   169  	}
   170  	for k, v := range h.labels {
   171  		if err := k8s.AddLabel(workload, k, v); err != nil {
   172  			return err
   173  		}
   174  	}
   175  	if err := h.handlers.Apply(multiCtx, cluster, WorkflowResourceCreator, workload); err != nil {
   176  		return err
   177  	}
   178  	return velacue.FillUnstructuredObject(v, workload, "result")
   179  }
   180  
   181  // Apply create or update CR in cluster.
   182  func (h *provider) Apply(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   183  	val, err := v.LookupValue("value")
   184  	if err != nil {
   185  		return err
   186  	}
   187  	var workload = new(unstructured.Unstructured)
   188  	if err := val.UnmarshalTo(workload); err != nil {
   189  		return err
   190  	}
   191  	if workload.GetNamespace() == "" {
   192  		workload.SetNamespace("default")
   193  	}
   194  	for k, v := range h.labels {
   195  		if err := k8s.AddLabel(workload, k, v); err != nil {
   196  			return err
   197  		}
   198  	}
   199  	cluster, err := v.GetString("cluster")
   200  	if err != nil {
   201  		return err
   202  	}
   203  	deployCtx := handleContext(ctx, cluster)
   204  	if err := h.handlers.Apply(deployCtx, cluster, WorkflowResourceCreator, workload); err != nil {
   205  		return err
   206  	}
   207  	return velacue.FillUnstructuredObject(v, workload, "value")
   208  }
   209  
   210  // ApplyInParallel create or update CRs in parallel.
   211  func (h *provider) ApplyInParallel(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   212  	val, err := v.LookupValue("value")
   213  	if err != nil {
   214  		return err
   215  	}
   216  	iter, err := val.CueValue().List()
   217  	if err != nil {
   218  		return err
   219  	}
   220  	workloadNum := 0
   221  	for iter.Next() {
   222  		workloadNum++
   223  	}
   224  	var workloads = make([]*unstructured.Unstructured, workloadNum)
   225  	if err = val.UnmarshalTo(&workloads); err != nil {
   226  		return err
   227  	}
   228  	for i := range workloads {
   229  		if workloads[i].GetNamespace() == "" {
   230  			workloads[i].SetNamespace("default")
   231  		}
   232  	}
   233  	cluster, err := v.GetString("cluster")
   234  	if err != nil {
   235  		return err
   236  	}
   237  	deployCtx := handleContext(ctx, cluster)
   238  	if err := h.handlers.Apply(deployCtx, cluster, WorkflowResourceCreator, workloads...); err != nil {
   239  		return err
   240  	}
   241  	return nil
   242  }
   243  
   244  // Read get CR from cluster.
   245  func (h *provider) Read(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   246  	val, err := v.LookupValue("value")
   247  	if err != nil {
   248  		return err
   249  	}
   250  	obj := new(unstructured.Unstructured)
   251  	if err := val.UnmarshalTo(obj); err != nil {
   252  		return err
   253  	}
   254  	key := client.ObjectKeyFromObject(obj)
   255  	if key.Namespace == "" {
   256  		key.Namespace = "default"
   257  	}
   258  	cluster, err := v.GetString("cluster")
   259  	if err != nil {
   260  		return err
   261  	}
   262  	readCtx := handleContext(ctx, cluster)
   263  	if err := h.cli.Get(readCtx, key, obj); err != nil {
   264  		return v.FillObject(err.Error(), "err")
   265  	}
   266  	return velacue.FillUnstructuredObject(v, obj, "value")
   267  }
   268  
   269  // List lists CRs from cluster.
   270  func (h *provider) List(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   271  	r, err := v.LookupValue("resource")
   272  	if err != nil {
   273  		return err
   274  	}
   275  	resource := &metav1.TypeMeta{}
   276  	if err := r.UnmarshalTo(resource); err != nil {
   277  		return err
   278  	}
   279  	list := &unstructured.UnstructuredList{Object: map[string]interface{}{
   280  		"kind":       resource.Kind,
   281  		"apiVersion": resource.APIVersion,
   282  	}}
   283  
   284  	filterValue, err := v.LookupValue("filter")
   285  	if err != nil {
   286  		return err
   287  	}
   288  	filter := &filters{}
   289  	if err := filterValue.UnmarshalTo(filter); err != nil {
   290  		return err
   291  	}
   292  	cluster, err := v.GetString("cluster")
   293  	if err != nil {
   294  		return err
   295  	}
   296  	listOpts := []client.ListOption{
   297  		client.InNamespace(filter.Namespace),
   298  		client.MatchingLabels(filter.MatchingLabels),
   299  	}
   300  	readCtx := handleContext(ctx, cluster)
   301  	if err := h.cli.List(readCtx, list, listOpts...); err != nil {
   302  		return v.FillObject(err.Error(), "err")
   303  	}
   304  	return velacue.FillUnstructuredObject(v, list, "list")
   305  }
   306  
   307  // Delete deletes CR from cluster.
   308  func (h *provider) Delete(ctx monitorContext.Context, wfCtx wfContext.Context, v *value.Value, act types.Action) error {
   309  	val, err := v.LookupValue("value")
   310  	if err != nil {
   311  		return err
   312  	}
   313  	obj := new(unstructured.Unstructured)
   314  	if err := val.UnmarshalTo(obj); err != nil {
   315  		return err
   316  	}
   317  	cluster, err := v.GetString("cluster")
   318  	if err != nil {
   319  		return err
   320  	}
   321  	deleteCtx := handleContext(ctx, cluster)
   322  
   323  	if filterValue, err := v.LookupValue("filter"); err == nil {
   324  		filter := &filters{}
   325  		if err := filterValue.UnmarshalTo(filter); err != nil {
   326  			return err
   327  		}
   328  		labelSelector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: filter.MatchingLabels})
   329  		if err != nil {
   330  			return err
   331  		}
   332  		if err := h.cli.DeleteAllOf(deleteCtx, obj, &client.DeleteAllOfOptions{ListOptions: client.ListOptions{Namespace: filter.Namespace, LabelSelector: labelSelector}}); err != nil {
   333  			return v.FillObject(err.Error(), "err")
   334  		}
   335  		return nil
   336  	}
   337  
   338  	if err := h.handlers.Delete(deleteCtx, cluster, WorkflowResourceCreator, obj); err != nil {
   339  		return v.FillObject(err.Error(), "err")
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  // Install register handlers to provider discover.
   346  func Install(p types.Providers, cli client.Client, labels map[string]string, handlers *Handlers) {
   347  	if handlers == nil {
   348  		d := &dispatcher{
   349  			cli: cli,
   350  		}
   351  		handlers = &Handlers{
   352  			Apply:  d.apply,
   353  			Delete: d.delete,
   354  		}
   355  	}
   356  	prd := &provider{
   357  		cli:      cli,
   358  		handlers: *handlers,
   359  		labels:   labels,
   360  	}
   361  	p.Register(ProviderName, map[string]types.Handler{
   362  		"apply":             prd.Apply,
   363  		"apply-in-parallel": prd.ApplyInParallel,
   364  		"read":              prd.Read,
   365  		"list":              prd.List,
   366  		"delete":            prd.Delete,
   367  		"patch":             prd.Patch,
   368  	})
   369  }