github.com/argoproj/argo-cd/v2@v2.10.9/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/antonmedv/expr" 10 "github.com/argoproj/gitops-engine/pkg/utils/kube" 11 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 "k8s.io/utils/pointer" 13 14 "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" 15 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 16 "github.com/argoproj/argo-cd/v2/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]interface{} { 66 deeplinkObj := map[string]interface{}{} 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]interface{}, name string, links []settings.DeepLink) (*application.LinksResponse, []string) { 84 finalLinks := []*application.LinkInfo{} 85 errors := []string{} 86 for _, link := range links { 87 t, err := template.New("deep-link").Funcs(sprigFuncMap).Parse(link.URL) 88 if err != nil { 89 errors = append(errors, fmt.Sprintf("failed to parse link template '%v', error=%v", link.URL, err.Error())) 90 continue 91 } 92 finalURL := bytes.Buffer{} 93 err = t.Execute(&finalURL, obj) 94 if err != nil { 95 errors = append(errors, fmt.Sprintf("failed to evaluate link template '%v' with resource %v, error=%v", link.URL, name, err.Error())) 96 continue 97 } 98 if link.Condition != nil { 99 out, err := expr.Eval(*link.Condition, obj) 100 if err != nil { 101 errors = append(errors, fmt.Sprintf("failed to evaluate link condition '%v' with resource %v, error=%v", *link.Condition, name, err.Error())) 102 continue 103 } 104 switch resOut := out.(type) { 105 case bool: 106 if resOut { 107 finalLinks = append(finalLinks, &application.LinkInfo{ 108 Title: pointer.String(link.Title), 109 Url: pointer.String(finalURL.String()), 110 Description: link.Description, 111 IconClass: link.IconClass, 112 }) 113 } 114 default: 115 errors = append(errors, fmt.Sprintf("link condition '%v' evaluated to non-boolean value for resource %v", *link.Condition, name)) 116 continue 117 } 118 } else { 119 finalLinks = append(finalLinks, &application.LinkInfo{ 120 Title: pointer.String(link.Title), 121 Url: pointer.String(finalURL.String()), 122 Description: link.Description, 123 IconClass: link.IconClass, 124 }) 125 } 126 } 127 return &application.LinksResponse{ 128 Items: finalLinks, 129 }, errors 130 }