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  }