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 }