github.com/oam-dev/kubevela@v1.9.11/references/docgen/cluster.go (about) 1 /* 2 Copyright 2021 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 docgen 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "github.com/pkg/errors" 25 kerrors "k8s.io/apimachinery/pkg/api/errors" 26 "k8s.io/apimachinery/pkg/api/meta" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/klog/v2" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 "github.com/kubevela/workflow/pkg/cue/packages" 33 34 commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 35 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 36 "github.com/oam-dev/kubevela/apis/types" 37 "github.com/oam-dev/kubevela/pkg/appfile" 38 "github.com/oam-dev/kubevela/pkg/cue" 39 "github.com/oam-dev/kubevela/pkg/definition" 40 "github.com/oam-dev/kubevela/pkg/oam/util" 41 "github.com/oam-dev/kubevela/pkg/utils" 42 "github.com/oam-dev/kubevela/pkg/utils/common" 43 "github.com/oam-dev/kubevela/references/docgen/fix" 44 ) 45 46 // DescriptionUndefined indicates the description is not defined 47 const DescriptionUndefined = "description not defined" 48 49 // GetCapabilitiesFromCluster will get capability from K8s cluster 50 func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, error) { 51 caps, erl, err := GetComponentsFromCluster(ctx, namespace, c, selector) 52 for _, er := range erl { 53 klog.Infof("get component capability %v", er) 54 } 55 if err != nil { 56 return nil, err 57 } 58 59 traits, erl, err := GetTraitsFromCluster(ctx, namespace, c, selector) 60 if err != nil { 61 return nil, err 62 } 63 for _, er := range erl { 64 klog.Infof("get trait capability %v", er) 65 } 66 caps = append(caps, traits...) 67 68 plcs, erl, err := GetPolicies(ctx, namespace, c) 69 if err != nil { 70 return nil, err 71 } 72 for _, er := range erl { 73 klog.Infof("get policy capability %v", er) 74 } 75 caps = append(caps, plcs...) 76 77 wfs, erl, err := GetWorkflowSteps(ctx, namespace, c) 78 if err != nil { 79 return nil, err 80 } 81 for _, er := range erl { 82 klog.Infof("get workflow step %v", er) 83 } 84 caps = append(caps, wfs...) 85 86 return caps, nil 87 } 88 89 // GetNamespacedCapabilitiesFromCluster will get capability from K8s cluster in the specified namespace and default namespace 90 // If the definition could be found from `namespace`, try to find in namespace `types.DefaultKubeVelaNS` 91 func GetNamespacedCapabilitiesFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, error) { 92 var capabilities []types.Capability 93 94 if workloads, _, err := GetComponentsFromClusterWithValidateOption(ctx, namespace, c, selector, false); err == nil { 95 capabilities = append(capabilities, workloads...) 96 } 97 98 if traits, _, err := GetTraitsFromClusterWithValidateOption(ctx, namespace, c, selector, false); err == nil { 99 capabilities = append(capabilities, traits...) 100 } 101 102 if workflowSteps, _, err := GetWorkflowSteps(ctx, namespace, c); err == nil { 103 capabilities = append(capabilities, workflowSteps...) 104 } 105 106 if policies, _, err := GetPolicies(ctx, namespace, c); err == nil { 107 capabilities = append(capabilities, policies...) 108 } 109 110 if namespace != types.DefaultKubeVelaNS { 111 // get components from default namespace 112 if workloads, _, err := GetComponentsFromClusterWithValidateOption(ctx, types.DefaultKubeVelaNS, c, selector, false); err == nil { 113 capabilities = append(capabilities, workloads...) 114 } 115 116 // get traits from default namespace 117 if traits, _, err := GetTraitsFromClusterWithValidateOption(ctx, types.DefaultKubeVelaNS, c, selector, false); err == nil { 118 capabilities = append(capabilities, traits...) 119 } 120 121 if workflowSteps, _, err := GetWorkflowSteps(ctx, types.DefaultKubeVelaNS, c); err == nil { 122 capabilities = append(capabilities, workflowSteps...) 123 } 124 125 if policies, _, err := GetPolicies(ctx, types.DefaultKubeVelaNS, c); err == nil { 126 capabilities = append(capabilities, policies...) 127 } 128 } 129 130 if len(capabilities) > 0 { 131 return capabilities, nil 132 } 133 return nil, fmt.Errorf("could not find any components, traits or workflowSteps from namespace %s and %s", namespace, types.DefaultKubeVelaNS) 134 } 135 136 // GetComponentsFromCluster will get capability from K8s cluster 137 func GetComponentsFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, []error, error) { 138 return GetComponentsFromClusterWithValidateOption(ctx, namespace, c, selector, true) 139 } 140 141 // GetComponentsFromClusterWithValidateOption will get capability from K8s cluster with an option whether to valid Components 142 func GetComponentsFromClusterWithValidateOption(ctx context.Context, namespace string, c common.Args, selector labels.Selector, validateFlag bool) ([]types.Capability, []error, error) { 143 newClient, err := c.GetClient() 144 if err != nil { 145 return nil, nil, err 146 } 147 148 var templates []types.Capability 149 var componentsDefs v1beta1.ComponentDefinitionList 150 err = newClient.List(ctx, &componentsDefs, &client.ListOptions{Namespace: namespace, LabelSelector: selector}) 151 if err != nil { 152 return nil, nil, fmt.Errorf("list ComponentDefinition err: %w", err) 153 } 154 155 var templateErrors []error 156 for _, cd := range componentsDefs.Items { 157 defRef := commontypes.DefinitionReference{ 158 Name: cd.Spec.Workload.Type, 159 } 160 if cd.Spec.Workload.Type != types.AutoDetectWorkloadDefinition { 161 defRef, err = util.ConvertWorkloadGVK2Definition(newClient.RESTMapper(), cd.Spec.Workload.Definition) 162 if err != nil { 163 return nil, nil, err 164 } 165 } 166 167 tmp, err := GetCapabilityByComponentDefinitionObject(cd, defRef.Name) 168 if err != nil { 169 templateErrors = append(templateErrors, err) 170 continue 171 } 172 if validateFlag && defRef.Name != types.AutoDetectWorkloadDefinition { 173 if err = validateCapabilities(newClient.RESTMapper(), cd.Name, defRef); err != nil { 174 return nil, nil, err 175 } 176 } 177 templates = append(templates, *tmp) 178 } 179 return templates, templateErrors, nil 180 } 181 182 // GetTraitsFromCluster will get capability from K8s cluster 183 func GetTraitsFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, []error, error) { 184 return GetTraitsFromClusterWithValidateOption(ctx, namespace, c, selector, true) 185 } 186 187 // GetTraitsFromClusterWithValidateOption will get capability from K8s cluster with an option whether to valid Traits 188 func GetTraitsFromClusterWithValidateOption(ctx context.Context, namespace string, c common.Args, selector labels.Selector, validateFlag bool) ([]types.Capability, []error, error) { 189 newClient, err := c.GetClient() 190 if err != nil { 191 return nil, nil, err 192 } 193 var templates []types.Capability 194 var traitDefs v1beta1.TraitDefinitionList 195 err = newClient.List(ctx, &traitDefs, &client.ListOptions{Namespace: namespace, LabelSelector: selector}) 196 if err != nil { 197 return nil, nil, fmt.Errorf("list TraitDefinition err: %w", err) 198 } 199 200 var templateErrors []error 201 for _, td := range traitDefs.Items { 202 var tmp *types.Capability 203 var err error 204 // FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed 205 if td.Name == "container-image" { 206 tmp = fix.CapContainerImage 207 } else { 208 tmp, err = GetCapabilityByTraitDefinitionObject(td) 209 if err != nil { 210 templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name)) 211 continue 212 } 213 } 214 tmp.Namespace = namespace 215 if validateFlag { 216 if err = validateCapabilities(newClient.RESTMapper(), td.Name, td.Spec.Reference); err != nil { 217 return nil, nil, err 218 } 219 } 220 templates = append(templates, *tmp) 221 } 222 return templates, templateErrors, nil 223 } 224 225 // GetWorkflowSteps will get WorkflowStepDefinition list 226 func GetWorkflowSteps(ctx context.Context, namespace string, c common.Args) ([]types.Capability, []error, error) { 227 newClient, err := c.GetClient() 228 if err != nil { 229 return nil, nil, err 230 } 231 232 var templates []types.Capability 233 var workflowStepDefs v1beta1.WorkflowStepDefinitionList 234 err = newClient.List(ctx, &workflowStepDefs, &client.ListOptions{Namespace: namespace}) 235 if err != nil { 236 return nil, nil, fmt.Errorf("list WorkflowStepDefinition err: %w", err) 237 } 238 239 config, err := c.GetConfig() 240 if err != nil { 241 return nil, nil, err 242 } 243 pd, err := packages.NewPackageDiscover(config) 244 if err != nil { 245 return nil, nil, err 246 } 247 248 var templateErrors []error 249 for _, def := range workflowStepDefs.Items { 250 tmp, err := GetCapabilityByWorkflowStepDefinitionObject(def, pd) 251 if err != nil { 252 templateErrors = append(templateErrors, errors.WithMessage(err, def.Name)) 253 continue 254 } 255 templates = append(templates, *tmp) 256 } 257 return templates, templateErrors, nil 258 } 259 260 // GetPolicies will get Policy from K8s cluster 261 func GetPolicies(ctx context.Context, namespace string, c common.Args) ([]types.Capability, []error, error) { 262 newClient, err := c.GetClient() 263 if err != nil { 264 return nil, nil, err 265 } 266 267 var templates []types.Capability 268 var defs v1beta1.PolicyDefinitionList 269 err = newClient.List(ctx, &defs, &client.ListOptions{Namespace: namespace}) 270 if err != nil { 271 return nil, nil, fmt.Errorf("list PolicyDefinition err: %w", err) 272 } 273 274 var templateErrors []error 275 for _, def := range defs.Items { 276 tmp, err := GetCapabilityByPolicyDefinitionObject(def, nil) 277 if err != nil { 278 templateErrors = append(templateErrors, err) 279 continue 280 } 281 templates = append(templates, *tmp) 282 } 283 return templates, templateErrors, nil 284 } 285 286 // validateCapabilities validates whether GVK are successfully retrieved. 287 func validateCapabilities(mapper meta.RESTMapper, definitionName string, reference commontypes.DefinitionReference) error { 288 _, err := util.GetGVKFromDefinition(mapper, reference) 289 if err != nil { 290 errMsg := err.Error() 291 var substr = "no matches for " 292 if strings.Contains(errMsg, substr) { 293 return fmt.Errorf("expected provider: %s", strings.Split(errMsg, substr)[1]) 294 } 295 return fmt.Errorf("installing capability '%s'... %w", definitionName, err) 296 } 297 return nil 298 } 299 300 // HandleDefinition will handle definition to capability 301 func HandleDefinition(name, crdName string, annotation, labels map[string]string, extension *runtime.RawExtension, tp types.CapType, 302 applyTo []string, schematic *commontypes.Schematic, pd *packages.PackageDiscover) (types.Capability, error) { 303 var tmp types.Capability 304 tmp, err := HandleTemplate(extension, schematic, name, pd) 305 if err != nil { 306 return types.Capability{}, err 307 } 308 tmp.Type = tp 309 if tp == types.TypeTrait { 310 tmp.AppliesTo = applyTo 311 } 312 tmp.CrdName = crdName 313 tmp.Description = GetDescription(annotation) 314 tmp.Example = GetExample(annotation) 315 tmp.Labels = labels 316 return tmp, nil 317 } 318 319 // GetDescription get description from annotation 320 func GetDescription(annotation map[string]string) string { 321 if annotation == nil { 322 return DescriptionUndefined 323 } 324 desc, ok := annotation[types.AnnoDefinitionDescription] 325 if !ok { 326 return DescriptionUndefined 327 } 328 desc = strings.ReplaceAll(desc, "\n", " ") 329 return desc 330 } 331 332 // GetExample get example markdown from annotation specified url 333 func GetExample(annotation map[string]string) string { 334 if annotation == nil { 335 return "" 336 } 337 examplePath, ok := annotation[types.AnnoDefinitionExampleURL] 338 if !ok { 339 return "" 340 } 341 if !utils.IsValidURL(examplePath) { 342 return "" 343 } 344 data, err := common.HTTPGetWithOption(context.Background(), examplePath, nil) 345 if err != nil { 346 return "" 347 } 348 if strings.HasSuffix(examplePath, ".yaml") { 349 return fmt.Sprintf("```yaml\n%s\n```", string(data)) 350 } 351 return string(data) 352 } 353 354 // HandleTemplate will handle definition template to capability 355 func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string, pd *packages.PackageDiscover) (types.Capability, error) { 356 tmp, err := appfile.ConvertTemplateJSON2Object(name, in, schematic) 357 if err != nil { 358 return types.Capability{}, err 359 } 360 tmp.Name = name 361 // if spec.template is not empty it should has the highest priority 362 if schematic != nil { 363 if schematic.CUE != nil { 364 tmp.CueTemplate = schematic.CUE.Template 365 tmp.CueTemplateURI = "" 366 } 367 if schematic.Terraform != nil { 368 tmp.Category = types.TerraformCategory 369 tmp.TerraformConfiguration = schematic.Terraform.Configuration 370 tmp.ConfigurationType = schematic.Terraform.Type 371 tmp.Path = schematic.Terraform.Path 372 return tmp, nil 373 } 374 } 375 if tmp.CueTemplateURI != "" { 376 b, err := common.HTTPGetWithOption(context.Background(), tmp.CueTemplateURI, nil) 377 if err != nil { 378 return types.Capability{}, err 379 } 380 tmp.CueTemplate = string(b) 381 } 382 if tmp.CueTemplate == "" { 383 return types.Capability{}, errors.New("template not exist in definition") 384 } 385 tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate, pd) 386 if err != nil && !errors.Is(err, cue.ErrParameterNotExist) { 387 return types.Capability{}, err 388 } 389 tmp.Category = types.CUECategory 390 return tmp, nil 391 } 392 393 // GetCapabilityByName gets capability by definition name 394 func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string, pd *packages.PackageDiscover) (*types.Capability, error) { 395 var ( 396 foundCapability bool 397 capability *types.Capability 398 err error 399 ) 400 401 newClient, err := c.GetClient() 402 if err != nil { 403 return nil, err 404 } 405 var componentDef v1beta1.ComponentDefinition 406 err = newClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: capabilityName}, &componentDef) 407 if err == nil { 408 foundCapability = true 409 } else if kerrors.IsNotFound(err) { 410 err = newClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: capabilityName}, &componentDef) 411 if err == nil { 412 foundCapability = true 413 } 414 } 415 416 if foundCapability { 417 var refName string 418 419 // if workload type of ComponentDefinition is unclear, 420 // set the DefinitionReference's Name to AutoDetectWorkloadDefinition 421 if componentDef.Spec.Workload.Type == types.AutoDetectWorkloadDefinition { 422 refName = types.AutoDetectWorkloadDefinition 423 } else { 424 ref, err := util.ConvertWorkloadGVK2Definition(newClient.RESTMapper(), componentDef.Spec.Workload.Definition) 425 if err != nil { 426 return nil, err 427 } 428 refName = ref.Name 429 } 430 431 capability, err = GetCapabilityByComponentDefinitionObject(componentDef, refName) 432 if err != nil { 433 return nil, err 434 } 435 return capability, nil 436 } 437 438 foundCapability = false 439 var traitDef v1beta1.TraitDefinition 440 err = newClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: capabilityName}, &traitDef) 441 if err == nil { 442 foundCapability = true 443 } else if kerrors.IsNotFound(err) { 444 err = newClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: capabilityName}, &traitDef) 445 if err == nil { 446 foundCapability = true 447 } 448 } 449 if foundCapability { 450 capability, err = GetCapabilityByTraitDefinitionObject(traitDef) 451 if err != nil { 452 return nil, err 453 } 454 return capability, nil 455 } 456 457 var wfStepDef v1beta1.WorkflowStepDefinition 458 err = newClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: capabilityName}, &wfStepDef) 459 if err == nil { 460 foundCapability = true 461 } else if kerrors.IsNotFound(err) { 462 err = newClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: capabilityName}, &wfStepDef) 463 if err == nil { 464 foundCapability = true 465 } 466 } 467 if foundCapability { 468 capability, err = GetCapabilityByWorkflowStepDefinitionObject(wfStepDef, pd) 469 if err != nil { 470 return nil, err 471 } 472 return capability, nil 473 } 474 475 if ns == types.DefaultKubeVelaNS { 476 return nil, fmt.Errorf("could not find %s in namespace %s", capabilityName, ns) 477 } 478 return nil, fmt.Errorf("could not find %s in namespace %s, or %s", capabilityName, ns, types.DefaultKubeVelaNS) 479 } 480 481 // GetCapabilityFromDefinitionRevision gets capabilities from the underlying Definition in DefinitionRevisions 482 func GetCapabilityFromDefinitionRevision(ctx context.Context, c common.Args, pd *packages.PackageDiscover, ns, defName string, r int64) (*types.Capability, error) { 483 k8sClient, err := c.GetClient() 484 if err != nil { 485 return nil, err 486 } 487 488 revs, err := definition.SearchDefinitionRevisions(ctx, k8sClient, ns, defName, "", r) 489 if err != nil { 490 return nil, err 491 } 492 // `ns` defaults to `default` in `vela show`, if user doesn't specify anything, 493 // which often is not the desired behavior. 494 // So we need to search again in the vela-system namespace, if no revisions found. 495 // This behavior is consistent with the code above in GetCapabilityByName(), which also does double-search. 496 if len(revs) == 0 && ns == "default" { 497 revs, err = definition.SearchDefinitionRevisions(ctx, k8sClient, types.DefaultKubeVelaNS, defName, "", r) 498 if err != nil { 499 return nil, err 500 } 501 } 502 if len(revs) == 0 { 503 return nil, fmt.Errorf("no %s with revision %d found in namespace %s or %s", defName, r, ns, types.DefaultKubeVelaNS) 504 } 505 506 rev := revs[0] 507 508 switch rev.Spec.DefinitionType { 509 case commontypes.ComponentType: 510 var refName string 511 componentDef := rev.Spec.ComponentDefinition 512 // if workload type of ComponentDefinition is unclear, 513 // set the DefinitionReference's Name to AutoDetectWorkloadDefinition 514 if componentDef.Spec.Workload.Type == types.AutoDetectWorkloadDefinition { 515 refName = types.AutoDetectWorkloadDefinition 516 } else { 517 ref, err := util.ConvertWorkloadGVK2Definition(k8sClient.RESTMapper(), componentDef.Spec.Workload.Definition) 518 if err != nil { 519 return nil, err 520 } 521 refName = ref.Name 522 } 523 return GetCapabilityByComponentDefinitionObject(componentDef, refName) 524 case commontypes.TraitType: 525 return GetCapabilityByTraitDefinitionObject(rev.Spec.TraitDefinition) 526 case commontypes.WorkflowStepType: 527 return GetCapabilityByWorkflowStepDefinitionObject(rev.Spec.WorkflowStepDefinition, pd) 528 default: 529 return nil, fmt.Errorf("unsupported type %s", rev.Spec.DefinitionType) 530 } 531 } 532 533 // GetCapabilityByComponentDefinitionObject gets capability by ComponentDefinition object 534 func GetCapabilityByComponentDefinitionObject(componentDef v1beta1.ComponentDefinition, referenceName string) (*types.Capability, error) { 535 capability, err := HandleDefinition(componentDef.Name, referenceName, componentDef.Annotations, componentDef.Labels, 536 componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic, nil) 537 if err != nil { 538 return nil, errors.Wrap(err, "failed to handle ComponentDefinition") 539 } 540 capability.Namespace = componentDef.Namespace 541 return &capability, nil 542 } 543 544 // GetCapabilityByTraitDefinitionObject gets capability by TraitDefinition object 545 func GetCapabilityByTraitDefinitionObject(traitDef v1beta1.TraitDefinition) (*types.Capability, error) { 546 var ( 547 capability types.Capability 548 err error 549 ) 550 capability, err = HandleDefinition(traitDef.Name, traitDef.Spec.Reference.Name, traitDef.Annotations, traitDef.Labels, 551 traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic, nil) 552 if err != nil { 553 return nil, errors.Wrap(err, "failed to handle TraitDefinition") 554 } 555 capability.Namespace = traitDef.Namespace 556 return &capability, nil 557 } 558 559 // GetCapabilityByWorkflowStepDefinitionObject gets capability by WorkflowStepDefinition object 560 func GetCapabilityByWorkflowStepDefinitionObject(wfStepDef v1beta1.WorkflowStepDefinition, pd *packages.PackageDiscover) (*types.Capability, error) { 561 capability, err := HandleDefinition(wfStepDef.Name, wfStepDef.Spec.Reference.Name, wfStepDef.Annotations, wfStepDef.Labels, 562 nil, types.TypeWorkflowStep, nil, wfStepDef.Spec.Schematic, pd) 563 if err != nil { 564 return nil, errors.Wrap(err, "failed to handle WorkflowStepDefinition") 565 } 566 capability.Namespace = wfStepDef.Namespace 567 return &capability, nil 568 } 569 570 // GetCapabilityByPolicyDefinitionObject gets capability by PolicyDefinition object 571 func GetCapabilityByPolicyDefinitionObject(def v1beta1.PolicyDefinition, pd *packages.PackageDiscover) (*types.Capability, error) { 572 capability, err := HandleDefinition(def.Name, def.Spec.Reference.Name, def.Annotations, def.Labels, 573 nil, types.TypePolicy, nil, def.Spec.Schematic, pd) 574 if err != nil { 575 return nil, errors.Wrap(err, "failed to handle PolicyDefinition") 576 } 577 capability.Namespace = def.Namespace 578 return &capability, nil 579 }