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  }