github.com/argoproj/argo-cd@v1.8.7/server/badge/badge.go (about)

     1  package badge
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"regexp"
     8  
     9  	healthutil "github.com/argoproj/gitops-engine/pkg/health"
    10  	"k8s.io/apimachinery/pkg/api/errors"
    11  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    14  	"github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
    15  	"github.com/argoproj/argo-cd/util/argo"
    16  	"github.com/argoproj/argo-cd/util/assets"
    17  	"github.com/argoproj/argo-cd/util/settings"
    18  )
    19  
    20  //NewHandler creates handler serving to do api/badge endpoint
    21  func NewHandler(appClientset versioned.Interface, settingsMrg *settings.SettingsManager, namespace string) http.Handler {
    22  	return &Handler{appClientset: appClientset, namespace: namespace, settingsMgr: settingsMrg}
    23  }
    24  
    25  //Handler used to get application in order to access health/sync
    26  type Handler struct {
    27  	namespace    string
    28  	appClientset versioned.Interface
    29  	settingsMgr  *settings.SettingsManager
    30  }
    31  
    32  var (
    33  	svgWidthPattern          = regexp.MustCompile(`^<svg width="([^"]*)"`)
    34  	displayNonePattern       = regexp.MustCompile(`display="none"`)
    35  	leftRectColorPattern     = regexp.MustCompile(`id="leftRect" fill="([^"]*)"`)
    36  	rightRectColorPattern    = regexp.MustCompile(`id="rightRect" fill="([^"]*)"`)
    37  	revisionRectColorPattern = regexp.MustCompile(`id="revisionRect" fill="([^"]*)"`)
    38  	leftTextPattern          = regexp.MustCompile(`id="leftText" [^>]*>([^<]*)`)
    39  	rightTextPattern         = regexp.MustCompile(`id="rightText" [^>]*>([^<]*)`)
    40  	revisionTextPattern      = regexp.MustCompile(`id="revisionText" [^>]*>([^<]*)`)
    41  )
    42  
    43  const (
    44  	svgWidthWithRevision = 192
    45  )
    46  
    47  func replaceFirstGroupSubMatch(re *regexp.Regexp, str string, repl string) string {
    48  	result := ""
    49  	lastIndex := 0
    50  
    51  	for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
    52  		groups := []string{}
    53  		for i := 0; i < len(v); i += 2 {
    54  			groups = append(groups, str[v[i]:v[i+1]])
    55  		}
    56  
    57  		result += str[lastIndex:v[0]] + groups[0] + repl
    58  		lastIndex = v[1]
    59  	}
    60  
    61  	return result + str[lastIndex:]
    62  }
    63  
    64  //ServeHTTP returns badge with health and sync status for application
    65  //(or an error badge if wrong query or application name is given)
    66  func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    67  	health := healthutil.HealthStatusUnknown
    68  	status := appv1.SyncStatusCodeUnknown
    69  	revision := ""
    70  	revisionEnabled := false
    71  	enabled := false
    72  	notFound := false
    73  	if sets, err := h.settingsMgr.GetSettings(); err == nil {
    74  		enabled = sets.StatusBadgeEnabled
    75  	}
    76  
    77  	//Sample url: http://localhost:8080/api/badge?name=123
    78  	if name, ok := r.URL.Query()["name"]; ok && enabled {
    79  		if app, err := h.appClientset.ArgoprojV1alpha1().Applications(h.namespace).Get(context.Background(), name[0], v1.GetOptions{}); err == nil {
    80  			health = app.Status.Health.Status
    81  			status = app.Status.Sync.Status
    82  			if app.Status.OperationState != nil && app.Status.OperationState.SyncResult != nil {
    83  				revision = app.Status.OperationState.SyncResult.Revision
    84  			}
    85  		} else if errors.IsNotFound(err) {
    86  			notFound = true
    87  		}
    88  	}
    89  	//Sample url: http://localhost:8080/api/badge?project=default
    90  	if projects, ok := r.URL.Query()["project"]; ok && enabled {
    91  		if apps, err := h.appClientset.ArgoprojV1alpha1().Applications(h.namespace).List(context.Background(), v1.ListOptions{}); err == nil {
    92  			applicationSet := argo.FilterByProjects(apps.Items, projects)
    93  			for _, a := range applicationSet {
    94  				if a.Status.Sync.Status != appv1.SyncStatusCodeSynced {
    95  					status = appv1.SyncStatusCodeOutOfSync
    96  				}
    97  				if a.Status.Health.Status != healthutil.HealthStatusHealthy {
    98  					health = healthutil.HealthStatusDegraded
    99  				}
   100  			}
   101  			if health != healthutil.HealthStatusDegraded && len(applicationSet) > 0 {
   102  				health = healthutil.HealthStatusHealthy
   103  			}
   104  			if status != appv1.SyncStatusCodeOutOfSync && len(applicationSet) > 0 {
   105  				status = appv1.SyncStatusCodeSynced
   106  			}
   107  		}
   108  	}
   109  	//Sample url: http://localhost:8080/api/badge?name=123&revision=true
   110  	if _, ok := r.URL.Query()["revision"]; ok && enabled {
   111  		revisionEnabled = true
   112  	}
   113  
   114  	leftColorString := ""
   115  	if leftColor, ok := HealthStatusColors[health]; ok {
   116  		leftColorString = toRGBString(leftColor)
   117  	} else {
   118  		leftColorString = toRGBString(Grey)
   119  	}
   120  
   121  	rightColorString := ""
   122  	if rightColor, ok := SyncStatusColors[status]; ok {
   123  		rightColorString = toRGBString(rightColor)
   124  	} else {
   125  		rightColorString = toRGBString(Grey)
   126  	}
   127  
   128  	leftText := string(health)
   129  	rightText := string(status)
   130  
   131  	if notFound {
   132  		leftText = "Not Found"
   133  		rightText = ""
   134  	}
   135  
   136  	badge := assets.BadgeSVG
   137  	badge = leftRectColorPattern.ReplaceAllString(badge, fmt.Sprintf(`id="leftRect" fill="%s" $2`, leftColorString))
   138  	badge = rightRectColorPattern.ReplaceAllString(badge, fmt.Sprintf(`id="rightRect" fill="%s" $2`, rightColorString))
   139  	badge = replaceFirstGroupSubMatch(leftTextPattern, badge, leftText)
   140  	badge = replaceFirstGroupSubMatch(rightTextPattern, badge, rightText)
   141  
   142  	if !notFound && revisionEnabled && revision != "" {
   143  		// Increase width of SVG and enable display of revision components
   144  		badge = svgWidthPattern.ReplaceAllString(badge, fmt.Sprintf(`<svg width="%d" $2`, svgWidthWithRevision))
   145  		badge = displayNonePattern.ReplaceAllString(badge, `display="inline"`)
   146  		badge = revisionRectColorPattern.ReplaceAllString(badge, fmt.Sprintf(`id="revisionRect" fill="%s" $2`, rightColorString))
   147  		shortRevision := revision
   148  		if len(shortRevision) > 7 {
   149  			shortRevision = shortRevision[:7]
   150  		}
   151  		badge = replaceFirstGroupSubMatch(revisionTextPattern, badge, fmt.Sprintf("(%s)", shortRevision))
   152  	}
   153  
   154  	w.Header().Set("Content-Type", "image/svg+xml")
   155  
   156  	//Ask cache's to not cache the contents in order prevent the badge from becoming stale
   157  	w.Header().Set("Cache-Control", "private, no-store")
   158  	w.WriteHeader(http.StatusOK)
   159  	_, _ = w.Write([]byte(badge))
   160  }