github.com/argoproj/argo-cd/v3@v3.2.1/server/deeplinks/deeplinks.go (about) 1 package deeplinks 2 3 import ( 4 "bytes" 5 "fmt" 6 "text/template" 7 8 "github.com/Masterminds/sprig/v3" 9 "github.com/argoproj/gitops-engine/pkg/utils/kube" 10 "github.com/expr-lang/expr" 11 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 "k8s.io/utils/ptr" 13 14 "github.com/argoproj/argo-cd/v3/pkg/apiclient/application" 15 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 16 "github.com/argoproj/argo-cd/v3/util/settings" 17 ) 18 19 var sprigFuncMap = sprig.GenericFuncMap() // a singleton for better performance 20 21 func init() { 22 // Avoid allowing the user to learn things about the environment. 23 delete(sprigFuncMap, "env") 24 delete(sprigFuncMap, "expandenv") 25 delete(sprigFuncMap, "getHostByName") 26 } 27 28 const ( 29 ResourceDeepLinkKey = "resource" 30 AppDeepLinkKey = "application" 31 AppDeepLinkShortKey = "app" 32 ClusterDeepLinkKey = "cluster" 33 ProjectDeepLinkKey = "project" 34 ) 35 36 type ClusterLinksData struct { 37 // Server is the API server URL of the Kubernetes cluster 38 Server string `json:"server" protobuf:"bytes,1,opt,name=server"` 39 // Name of the cluster. If omitted, will use the server address 40 Name string `json:"name" protobuf:"bytes,2,opt,name=name"` 41 // Holds list of namespaces which are accessible in that cluster. Cluster level resources will be ignored if namespace list is not empty. 42 Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,6,opt,name=namespaces"` 43 // Shard contains optional shard number. Calculated on the fly by the application controller if not specified. 44 Shard *int64 `json:"shard,omitempty" protobuf:"bytes,9,opt,name=shard"` 45 // Reference between project and cluster that allow you automatically to be added as item inside Destinations project entity 46 Project string `json:"project,omitempty" protobuf:"bytes,11,opt,name=project"` 47 // Labels for cluster secret metadata 48 Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,12,opt,name=labels"` 49 // Annotations for cluster secret metadata 50 Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,13,opt,name=annotations"` 51 } 52 53 func SanitizeCluster(cluster *v1alpha1.Cluster) (*unstructured.Unstructured, error) { 54 return kube.ToUnstructured(&ClusterLinksData{ 55 Server: cluster.Server, 56 Name: cluster.Name, 57 Namespaces: cluster.Namespaces, 58 Shard: cluster.Shard, 59 Project: cluster.Project, 60 Labels: cluster.Labels, 61 Annotations: cluster.Annotations, 62 }) 63 } 64 65 func CreateDeepLinksObject(resourceObj *unstructured.Unstructured, app *unstructured.Unstructured, cluster *unstructured.Unstructured, project *unstructured.Unstructured) map[string]any { 66 deeplinkObj := map[string]any{} 67 if resourceObj != nil { 68 deeplinkObj[ResourceDeepLinkKey] = resourceObj.Object 69 } 70 if app != nil { 71 deeplinkObj[AppDeepLinkKey] = app.Object 72 deeplinkObj[AppDeepLinkShortKey] = app.Object 73 } 74 if cluster != nil { 75 deeplinkObj[ClusterDeepLinkKey] = cluster.Object 76 } 77 if project != nil { 78 deeplinkObj[ProjectDeepLinkKey] = project.Object 79 } 80 return deeplinkObj 81 } 82 83 func EvaluateDeepLinksResponse(obj map[string]any, name string, links []settings.DeepLink) (*application.LinksResponse, []string) { 84 finalLinks := []*application.LinkInfo{} 85 errors := []string{} 86 for _, link := range links { 87 if link.Condition != nil { 88 out, err := expr.Eval(*link.Condition, obj) 89 if err != nil { 90 errors = append(errors, fmt.Sprintf("failed to evaluate link condition '%v' with resource %v, error=%v", *link.Condition, name, err.Error())) 91 continue 92 } 93 switch condResult := out.(type) { 94 case bool: 95 if !condResult { 96 continue 97 } 98 default: 99 errors = append(errors, fmt.Sprintf("link condition '%v' evaluated to non-boolean value for resource %v", *link.Condition, name)) 100 continue 101 } 102 } 103 104 t, err := template.New("deep-link").Funcs(sprigFuncMap).Parse(link.URL) 105 if err != nil { 106 errors = append(errors, fmt.Sprintf("failed to parse link template '%v', error=%v", link.URL, err.Error())) 107 continue 108 } 109 finalURL := bytes.Buffer{} 110 err = t.Execute(&finalURL, obj) 111 if err != nil { 112 errors = append(errors, fmt.Sprintf("failed to evaluate link template '%v' with resource %v, error=%v", link.URL, name, err.Error())) 113 continue 114 } 115 116 finalLinks = append(finalLinks, &application.LinkInfo{ 117 Title: ptr.To(link.Title), 118 Url: ptr.To(finalURL.String()), 119 Description: link.Description, 120 IconClass: link.IconClass, 121 }) 122 } 123 return &application.LinksResponse{ 124 Items: finalLinks, 125 }, errors 126 }