github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/generator.go (about) 1 /*Copyright 2021 The KubeVela Authors. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package application 17 18 import ( 19 "context" 20 "encoding/json" 21 "os" 22 "strings" 23 "time" 24 25 "github.com/pkg/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 "k8s.io/klog/v2" 30 "k8s.io/utils/pointer" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 monitorContext "github.com/kubevela/pkg/monitor/context" 34 pkgmulticluster "github.com/kubevela/pkg/multicluster" 35 "github.com/kubevela/pkg/util/slices" 36 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 37 "github.com/kubevela/workflow/pkg/cue/model/value" 38 "github.com/kubevela/workflow/pkg/executor" 39 "github.com/kubevela/workflow/pkg/generator" 40 "github.com/kubevela/workflow/pkg/providers" 41 "github.com/kubevela/workflow/pkg/providers/kube" 42 wfTypes "github.com/kubevela/workflow/pkg/types" 43 44 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 45 "github.com/oam-dev/kubevela/apis/core.oam.dev/condition" 46 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 47 "github.com/oam-dev/kubevela/apis/types" 48 "github.com/oam-dev/kubevela/pkg/appfile" 49 "github.com/oam-dev/kubevela/pkg/auth" 50 configprovider "github.com/oam-dev/kubevela/pkg/config/provider" 51 "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/application/assemble" 52 ctrlutil "github.com/oam-dev/kubevela/pkg/controller/utils" 53 velaprocess "github.com/oam-dev/kubevela/pkg/cue/process" 54 "github.com/oam-dev/kubevela/pkg/features" 55 "github.com/oam-dev/kubevela/pkg/monitor/metrics" 56 "github.com/oam-dev/kubevela/pkg/multicluster" 57 "github.com/oam-dev/kubevela/pkg/oam" 58 "github.com/oam-dev/kubevela/pkg/oam/util" 59 "github.com/oam-dev/kubevela/pkg/stdlib" 60 "github.com/oam-dev/kubevela/pkg/utils/apply" 61 "github.com/oam-dev/kubevela/pkg/velaql/providers/query" 62 multiclusterProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/multicluster" 63 oamProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/oam" 64 terraformProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/terraform" 65 "github.com/oam-dev/kubevela/pkg/workflow/template" 66 ) 67 68 func init() { 69 if err := stdlib.SetupBuiltinImports(); err != nil { 70 klog.ErrorS(err, "Unable to set up builtin imports on package initialization") 71 os.Exit(1) 72 } 73 } 74 75 var ( 76 // DisableResourceApplyDoubleCheck optimize applyComponentFunc by disable post resource existing check after dispatch 77 DisableResourceApplyDoubleCheck = false 78 ) 79 80 // GenerateApplicationSteps generate application steps. 81 // nolint:gocyclo 82 func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context, 83 app *v1beta1.Application, 84 appParser *appfile.Parser, 85 af *appfile.Appfile) (*wfTypes.WorkflowInstance, []wfTypes.TaskRunner, error) { 86 87 appRev := h.currentAppRev 88 t := time.Now() 89 defer func() { 90 metrics.AppReconcileStageDurationHistogram.WithLabelValues("generate-app-steps").Observe(time.Since(t).Seconds()) 91 }() 92 93 appLabels := map[string]string{ 94 oam.LabelAppName: app.Name, 95 oam.LabelAppNamespace: app.Namespace, 96 } 97 handlerProviders := providers.NewProviders() 98 kube.Install(handlerProviders, h.Client, appLabels, &kube.Handlers{ 99 Apply: h.Dispatch, 100 Delete: h.Delete, 101 }) 102 configprovider.Install(handlerProviders, h.Client, func(ctx context.Context, resources []*unstructured.Unstructured, applyOptions []apply.ApplyOption) error { 103 for _, res := range resources { 104 res.SetLabels(util.MergeMapOverrideWithDst(res.GetLabels(), appLabels)) 105 } 106 return h.resourceKeeper.Dispatch(ctx, resources, applyOptions) 107 }) 108 oamProvider.Install(handlerProviders, app, af, h.Client, 109 h.applyComponentFunc(appParser, appRev, af), 110 h.renderComponentFunc(appParser, appRev, af), 111 ) 112 pCtx := velaprocess.NewContext(generateContextDataFromApp(app, appRev.Name)) 113 renderer := func(ctx context.Context, comp common.ApplicationComponent) (*appfile.Component, error) { 114 return appParser.ParseComponentFromRevisionAndClient(ctx, comp, appRev) 115 } 116 multiclusterProvider.Install(handlerProviders, h.Client, app, af, 117 h.applyComponentFunc(appParser, appRev, af), 118 h.checkComponentHealth(appParser, appRev, af), 119 renderer) 120 terraformProvider.Install(handlerProviders, app, renderer) 121 query.Install(handlerProviders, h.Client, nil) 122 123 instance := generateWorkflowInstance(af, app) 124 executor.InitializeWorkflowInstance(instance) 125 runners, err := generator.GenerateRunners(ctx, instance, wfTypes.StepGeneratorOptions{ 126 Providers: handlerProviders, 127 PackageDiscover: h.pd, 128 ProcessCtx: pCtx, 129 TemplateLoader: template.NewWorkflowStepTemplateRevisionLoader(appRev, h.Client.RESTMapper()), 130 Client: h.Client, 131 StepConvertor: map[string]func(step workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error){ 132 wfTypes.WorkflowStepTypeApplyComponent: func(lstep workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error) { 133 copierStep := lstep.DeepCopy() 134 if err := convertStepProperties(copierStep, app); err != nil { 135 return lstep, errors.WithMessage(err, "convert [apply-component]") 136 } 137 copierStep.Type = wfTypes.WorkflowStepTypeBuiltinApplyComponent 138 return *copierStep, nil 139 }, 140 }, 141 }) 142 if err != nil { 143 return nil, nil, err 144 } 145 return instance, runners, nil 146 } 147 148 // CheckWorkflowRestart check if application workflow need restart and return the desired 149 // rev to be set in status 150 // 1. If workflow status is empty, it means no previous running record, the 151 // workflow will restart (cold start) 152 // 2. If workflow status is not empty, and publishVersion is set, the desired 153 // rev will be the publishVersion 154 // 3. If workflow status is not empty, the desired rev will be the 155 // ApplicationRevision name. For backward compatibility, the legacy style 156 // <rev>:<hash> will be recognized and reduced into <rev> 157 func (h *AppHandler) CheckWorkflowRestart(ctx monitorContext.Context, app *v1beta1.Application) { 158 desiredRev, currentRev := h.currentAppRev.Name, "" 159 if app.Status.Workflow != nil { 160 currentRev = app.Status.Workflow.AppRevision 161 } 162 if metav1.HasAnnotation(app.ObjectMeta, oam.AnnotationPublishVersion) { 163 desiredRev = app.GetAnnotations()[oam.AnnotationPublishVersion] 164 } else { // nolint 165 // backward compatibility 166 // legacy versions use <rev>:<hash> as currentRev, extract <rev> 167 if idx := strings.LastIndexAny(currentRev, ":"); idx >= 0 { 168 currentRev = currentRev[:idx] 169 } 170 } 171 if currentRev != "" && desiredRev == currentRev { 172 return 173 } 174 // record in revision 175 if h.latestAppRev != nil && h.latestAppRev.Status.Workflow == nil && app.Status.Workflow != nil { 176 app.Status.Workflow.Terminated = true 177 app.Status.Workflow.Finished = true 178 if app.Status.Workflow.EndTime.IsZero() { 179 app.Status.Workflow.EndTime = metav1.Now() 180 } 181 h.UpdateApplicationRevisionStatus(ctx, h.latestAppRev, app.Status.Workflow) 182 } 183 184 // clean recorded resources info. 185 app.Status.Services = nil 186 app.Status.AppliedResources = nil 187 188 // clean conditions after render 189 var reservedConditions []condition.Condition 190 for i, cond := range app.Status.Conditions { 191 condTpy, err := common.ParseApplicationConditionType(string(cond.Type)) 192 if err == nil { 193 if condTpy <= common.RenderCondition { 194 reservedConditions = append(reservedConditions, app.Status.Conditions[i]) 195 } 196 } 197 } 198 app.Status.Conditions = reservedConditions 199 app.Status.Workflow = &common.WorkflowStatus{ 200 AppRevision: desiredRev, 201 } 202 } 203 204 func generateWorkflowInstance(af *appfile.Appfile, app *v1beta1.Application) *wfTypes.WorkflowInstance { 205 instance := &wfTypes.WorkflowInstance{ 206 WorkflowMeta: wfTypes.WorkflowMeta{ 207 Name: af.Name, 208 Namespace: af.Namespace, 209 Annotations: app.Annotations, 210 Labels: app.Labels, 211 UID: app.UID, 212 ChildOwnerReferences: []metav1.OwnerReference{ 213 { 214 APIVersion: v1beta1.SchemeGroupVersion.String(), 215 Kind: v1beta1.ApplicationKind, 216 Name: app.Name, 217 UID: app.GetUID(), 218 Controller: pointer.Bool(true), 219 }, 220 }, 221 }, 222 Debug: af.Debug, 223 Steps: af.WorkflowSteps, 224 Mode: af.WorkflowMode, 225 } 226 status := app.Status.Workflow 227 instance.Status = workflowv1alpha1.WorkflowRunStatus{ 228 Mode: *af.WorkflowMode, 229 Phase: status.Phase, 230 Message: status.Message, 231 Suspend: status.Suspend, 232 SuspendState: status.SuspendState, 233 Terminated: status.Terminated, 234 Finished: status.Finished, 235 ContextBackend: status.ContextBackend, 236 Steps: status.Steps, 237 StartTime: status.StartTime, 238 EndTime: status.EndTime, 239 } 240 switch app.Status.Phase { 241 case common.ApplicationRunning: 242 instance.Status.Phase = workflowv1alpha1.WorkflowStateSucceeded 243 case common.ApplicationWorkflowSuspending: 244 instance.Status.Phase = workflowv1alpha1.WorkflowStateSuspending 245 case common.ApplicationWorkflowTerminated: 246 instance.Status.Phase = workflowv1alpha1.WorkflowStateTerminated 247 default: 248 instance.Status.Phase = workflowv1alpha1.WorkflowStateExecuting 249 } 250 return instance 251 } 252 253 func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.Application) error { 254 o := struct { 255 Component string `json:"component"` 256 Cluster string `json:"cluster"` 257 Namespace string `json:"namespace"` 258 }{} 259 js, err := common.RawExtensionPointer{RawExtension: step.Properties}.MarshalJSON() 260 if err != nil { 261 return err 262 } 263 if err := json.Unmarshal(js, &o); err != nil { 264 return err 265 } 266 267 var componentNames []string 268 for _, c := range app.Spec.Components { 269 componentNames = append(componentNames, c.Name) 270 } 271 272 for _, c := range app.Spec.Components { 273 if c.Name == o.Component { 274 if dcName, ok := checkDependsOnValidComponent(c.DependsOn, componentNames); !ok { 275 return errors.Errorf("component %s not found, which is depended by %s", dcName, c.Name) 276 } 277 step.Inputs = append(step.Inputs, c.Inputs...) 278 for index := range step.Inputs { 279 parameterKey := strings.TrimSpace(step.Inputs[index].ParameterKey) 280 if parameterKey != "" && !strings.HasPrefix(parameterKey, "properties") && !strings.HasPrefix(parameterKey, "traits[") { 281 parameterKey = "properties." + parameterKey 282 } 283 if parameterKey != "" { 284 parameterKey = "value." + parameterKey 285 } 286 step.Inputs[index].ParameterKey = parameterKey 287 } 288 step.Outputs = append(step.Outputs, c.Outputs...) 289 step.DependsOn = append(step.DependsOn, c.DependsOn...) 290 c.Inputs = nil 291 c.Outputs = nil 292 c.DependsOn = nil 293 stepProperties := map[string]interface{}{ 294 "value": c, 295 "cluster": o.Cluster, 296 } 297 if o.Namespace != "" { 298 stepProperties["namespace"] = o.Namespace 299 } 300 step.Properties = util.Object2RawExtension(stepProperties) 301 return nil 302 } 303 } 304 return errors.Errorf("component %s not found", o.Component) 305 } 306 307 func checkDependsOnValidComponent(dependsOnComponentNames, allComponentNames []string) (string, bool) { 308 // does not depend on other components 309 if dependsOnComponentNames == nil { 310 return "", true 311 } 312 for _, dc := range dependsOnComponentNames { 313 if !slices.Contains(allComponentNames, dc) { 314 return dc, false 315 } 316 } 317 return "", true 318 } 319 320 func (h *AppHandler) renderComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentRender { 321 return func(baseCtx context.Context, comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) { 322 ctx := multicluster.ContextWithClusterName(baseCtx, clusterName) 323 324 _, manifest, err := h.prepareWorkloadAndManifests(ctx, appParser, comp, appRev, patcher, af) 325 if err != nil { 326 return nil, nil, err 327 } 328 return renderComponentsAndTraits(manifest, appRev, clusterName, overrideNamespace) 329 } 330 } 331 332 func (h *AppHandler) checkComponentHealth(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentHealthCheck { 333 return func(baseCtx context.Context, comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) { 334 ctx := multicluster.ContextWithClusterName(baseCtx, clusterName) 335 ctx = contextWithComponentNamespace(ctx, overrideNamespace) 336 ctx = contextWithReplicaKey(ctx, comp.ReplicaKey) 337 338 wl, manifest, err := h.prepareWorkloadAndManifests(ctx, appParser, comp, appRev, patcher, af) 339 if err != nil { 340 return false, nil, nil, err 341 } 342 wl.Ctx.SetCtx(auth.ContextWithUserInfo(ctx, h.app)) 343 344 readyWorkload, readyTraits, err := renderComponentsAndTraits(manifest, appRev, clusterName, overrideNamespace) 345 if err != nil { 346 return false, nil, nil, err 347 } 348 checkSkipApplyWorkload(wl) 349 350 dispatchResources := readyTraits 351 if !wl.SkipApplyWorkload { 352 dispatchResources = append([]*unstructured.Unstructured{readyWorkload}, readyTraits...) 353 } 354 if !h.resourceKeeper.ContainsResources(dispatchResources) { 355 return false, nil, nil, err 356 } 357 358 _, output, outputs, isHealth, err := h.collectHealthStatus(auth.ContextWithUserInfo(ctx, h.app), wl, appRev, overrideNamespace, false) 359 if err != nil { 360 return false, nil, nil, err 361 } 362 363 return isHealth, output, outputs, err 364 } 365 } 366 367 func (h *AppHandler) applyComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentApply { 368 return func(baseCtx context.Context, comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) { 369 t := time.Now() 370 defer func() { metrics.ApplyComponentTimeHistogram.WithLabelValues("-").Observe(time.Since(t).Seconds()) }() 371 372 ctx := multicluster.ContextWithClusterName(baseCtx, clusterName) 373 ctx = contextWithComponentNamespace(ctx, overrideNamespace) 374 ctx = contextWithReplicaKey(ctx, comp.ReplicaKey) 375 376 wl, manifest, err := h.prepareWorkloadAndManifests(ctx, appParser, comp, appRev, patcher, af) 377 if err != nil { 378 return nil, nil, false, err 379 } 380 wl.Ctx.SetCtx(auth.ContextWithUserInfo(ctx, h.app)) 381 382 readyWorkload, readyTraits, err := renderComponentsAndTraits(manifest, appRev, clusterName, overrideNamespace) 383 if err != nil { 384 return nil, nil, false, err 385 } 386 checkSkipApplyWorkload(wl) 387 388 isHealth := true 389 if utilfeature.DefaultMutableFeatureGate.Enabled(features.MultiStageComponentApply) { 390 manifestDispatchers, err := h.generateDispatcher(appRev, readyWorkload, readyTraits, overrideNamespace) 391 if err != nil { 392 return nil, nil, false, errors.WithMessage(err, "generateDispatcher") 393 } 394 395 for _, dispatcher := range manifestDispatchers { 396 if isHealth, err := dispatcher.run(ctx, wl, appRev, clusterName); !isHealth || err != nil { 397 return nil, nil, false, err 398 } 399 } 400 } else { 401 dispatchResources := readyTraits 402 if !wl.SkipApplyWorkload { 403 dispatchResources = append([]*unstructured.Unstructured{readyWorkload}, readyTraits...) 404 } 405 406 if err := h.Dispatch(ctx, clusterName, common.WorkflowResourceCreator, dispatchResources...); err != nil { 407 return nil, nil, false, errors.WithMessage(err, "Dispatch") 408 } 409 _, _, _, isHealth, err = h.collectHealthStatus(ctx, wl, appRev, overrideNamespace, false) 410 if err != nil { 411 return nil, nil, false, errors.WithMessage(err, "CollectHealthStatus") 412 } 413 } 414 415 if DisableResourceApplyDoubleCheck { 416 return readyWorkload, readyTraits, isHealth, nil 417 } 418 workload, traits, err := getComponentResources(auth.ContextWithUserInfo(ctx, h.app), manifest, wl.SkipApplyWorkload, h.Client) 419 return workload, traits, isHealth, err 420 } 421 } 422 423 // redirectTraitToLocalIfNeed will override cluster field to be local for traits which are control plane only 424 func redirectTraitToLocalIfNeed(appRev *v1beta1.ApplicationRevision, readyTraits []*unstructured.Unstructured) []*unstructured.Unstructured { 425 traits := readyTraits 426 for index, readyTrait := range readyTraits { 427 for _, trait := range appRev.Spec.TraitDefinitions { 428 if trait.Spec.ControlPlaneOnly && trait.Name == readyTrait.GetLabels()[oam.TraitTypeLabel] { 429 oam.SetCluster(traits[index], multicluster.ClusterLocalName) 430 traits[index].SetNamespace(appRev.GetNamespace()) 431 break 432 } 433 } 434 } 435 return traits 436 } 437 438 func (h *AppHandler) prepareWorkloadAndManifests(ctx context.Context, 439 appParser *appfile.Parser, 440 comp common.ApplicationComponent, 441 appRev *v1beta1.ApplicationRevision, 442 patcher *value.Value, 443 af *appfile.Appfile) (*appfile.Component, *types.ComponentManifest, error) { 444 wl, err := appParser.ParseComponentFromRevisionAndClient(ctx, comp, appRev) 445 if err != nil { 446 return nil, nil, errors.WithMessage(err, "ParseWorkload") 447 } 448 wl.Patch = patcher 449 manifest, err := af.GenerateComponentManifest(wl, func(ctxData *velaprocess.ContextData) { 450 if ns := componentNamespaceFromContext(ctx); ns != "" { 451 ctxData.Namespace = ns 452 } 453 if rk := replicaKeyFromContext(ctx); rk != "" { 454 ctxData.ReplicaKey = rk 455 } 456 ctxData.Cluster = pkgmulticluster.Local 457 if cluster, ok := pkgmulticluster.ClusterFrom(ctx); ok && cluster != "" { 458 ctxData.Cluster = cluster 459 } 460 // cluster info are secrets stored in the control plane cluster 461 ctxData.ClusterVersion = multicluster.GetVersionInfoFromObject(pkgmulticluster.WithCluster(ctx, types.ClusterLocalName), h.Client, ctxData.Cluster) 462 ctxData.CompRevision, _ = ctrlutil.ComputeSpecHash(comp) 463 }) 464 if err != nil { 465 return nil, nil, errors.WithMessage(err, "GenerateComponentManifest") 466 } 467 if err := af.SetOAMContract(manifest); err != nil { 468 return nil, nil, errors.WithMessage(err, "SetOAMContract") 469 } 470 return wl, manifest, nil 471 } 472 473 func renderComponentsAndTraits(manifest *types.ComponentManifest, appRev *v1beta1.ApplicationRevision, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) { 474 readyWorkload, readyTraits, err := assemble.PrepareBeforeApply(manifest, appRev) 475 if err != nil { 476 return nil, nil, errors.WithMessage(err, "assemble resources before apply fail") 477 } 478 if clusterName != "" { 479 oam.SetClusterIfEmpty(readyWorkload, clusterName) 480 for _, readyTrait := range readyTraits { 481 oam.SetClusterIfEmpty(readyTrait, clusterName) 482 } 483 } 484 if overrideNamespace != "" { 485 readyWorkload.SetNamespace(overrideNamespace) 486 for _, readyTrait := range readyTraits { 487 readyTrait.SetNamespace(overrideNamespace) 488 } 489 } 490 readyTraits = redirectTraitToLocalIfNeed(appRev, readyTraits) 491 return readyWorkload, readyTraits, nil 492 } 493 494 func checkSkipApplyWorkload(comp *appfile.Component) { 495 for _, trait := range comp.Traits { 496 if trait.FullTemplate.TraitDefinition.Spec.ManageWorkload { 497 comp.SkipApplyWorkload = true 498 break 499 } 500 } 501 } 502 503 func getComponentResources(ctx context.Context, manifest *types.ComponentManifest, skipStandardWorkload bool, cli client.Client) (*unstructured.Unstructured, []*unstructured.Unstructured, error) { 504 var ( 505 workload *unstructured.Unstructured 506 traits []*unstructured.Unstructured 507 ) 508 if !skipStandardWorkload { 509 v := manifest.ComponentOutput.DeepCopy() 510 if err := cli.Get(ctx, client.ObjectKeyFromObject(manifest.ComponentOutput), v); err != nil { 511 return nil, nil, err 512 } 513 workload = v 514 } 515 516 for _, trait := range manifest.ComponentOutputsAndTraits { 517 v := trait.DeepCopy() 518 remoteCtx := multicluster.ContextWithClusterName(ctx, oam.GetCluster(v)) 519 if err := cli.Get(remoteCtx, client.ObjectKeyFromObject(trait), v); err != nil { 520 return workload, nil, err 521 } 522 traits = append(traits, v) 523 } 524 return workload, traits, nil 525 } 526 527 func generateContextDataFromApp(app *v1beta1.Application, appRev string) velaprocess.ContextData { 528 data := velaprocess.ContextData{ 529 Namespace: app.Namespace, 530 AppName: app.Name, 531 CompName: app.Name, 532 AppRevisionName: appRev, 533 } 534 if app.Annotations != nil { 535 data.WorkflowName = app.Annotations[oam.AnnotationWorkflowName] 536 data.PublishVersion = app.Annotations[oam.AnnotationPublishVersion] 537 } 538 return data 539 }