Skip to content

Commit

Permalink
feature: allow a custom labels on any resource to surface in the UI (…
Browse files Browse the repository at this point in the history
…tree view node tags) (argoproj#11153)

* feature: allow a custom label on any resource to surface the UI

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
Signed-off-by: emirot <emirot.nolan@gmail.com>
  • Loading branch information
alexef authored and emirot committed Jan 27, 2023
1 parent f75ca6b commit 2d18404
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 17 deletions.
7 changes: 6 additions & 1 deletion controller/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
}

resourceCustomLabels, err := c.settingsMgr.GetResourceCustomLabels()
if err != nil {
return nil, fmt.Errorf("error getting custom label: %w", err)
}

clusterCacheOpts := []clustercache.UpdateSettingsFunc{
clustercache.SetListSemaphore(semaphore.NewWeighted(clusterCacheListSemaphoreSize)),
clustercache.SetListPageSize(clusterCacheListPageSize),
Expand All @@ -400,7 +405,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
clustercache.SetClusterResources(cluster.ClusterResources),
clustercache.SetPopulateResourceInfoHandler(func(un *unstructured.Unstructured, isRoot bool) (interface{}, bool) {
res := &ResourceInfo{}
populateNodeInfo(un, res)
populateNodeInfo(un, res, resourceCustomLabels)
c.lock.RLock()
cacheSettings := c.cacheSettings
c.lock.RUnlock()
Expand Down
11 changes: 10 additions & 1 deletion controller/cache/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ import (
"github.com/argoproj/argo-cd/v2/util/resource"
)

func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo, customLabels []string) {
gvk := un.GroupVersionKind()
revision := resource.GetRevision(un)
if revision > 0 {
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Revision", Value: fmt.Sprintf("Rev:%v", revision)})
}
if len(customLabels) > 0 {
if labels := un.GetLabels(); labels != nil {
for _, customLabel := range customLabels {
if value, ok := labels[customLabel]; ok {
res.Info = append(res.Info, v1alpha1.InfoItem{Name: customLabel, Value: value})
}
}
}
}
switch gvk.Group {
case "":
switch gvk.Kind {
Expand Down
76 changes: 61 additions & 15 deletions controller/cache/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func TestGetPodInfo(t *testing.T) {
`)

info := &ResourceInfo{}
populateNodeInfo(pod, info)
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
Expand Down Expand Up @@ -302,7 +302,7 @@ status:
`)

info := &ResourceInfo{}
populateNodeInfo(node, info)
populateNodeInfo(node, info, []string{})
assert.Equal(t, &NodeInfo{
Name: "minikube",
Capacity: v1.ResourceList{v1.ResourceMemory: resource.MustParse("6091320Ki"), v1.ResourceCPU: resource.MustParse("6")},
Expand All @@ -312,7 +312,7 @@ status:

func TestGetServiceInfo(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testService, info)
populateNodeInfo(testService, info, []string{})
assert.Equal(t, 0, len(info.Info))
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
TargetLabels: map[string]string{"app": "guestbook"},
Expand All @@ -322,7 +322,7 @@ func TestGetServiceInfo(t *testing.T) {

func TestGetLinkAnnotatedServiceInfo(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testLinkAnnotatedService, info)
populateNodeInfo(testLinkAnnotatedService, info, []string{})
assert.Equal(t, 0, len(info.Info))
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
TargetLabels: map[string]string{"app": "guestbook"},
Expand All @@ -333,7 +333,7 @@ func TestGetLinkAnnotatedServiceInfo(t *testing.T) {

func TestGetIstioVirtualServiceInfo(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testIstioVirtualService, info)
populateNodeInfo(testIstioVirtualService, info, []string{})
assert.Equal(t, 0, len(info.Info))
require.NotNil(t, info.NetworkingInfo)
require.NotNil(t, info.NetworkingInfo.TargetRefs)
Expand Down Expand Up @@ -363,7 +363,7 @@ func TestGetIngressInfo(t *testing.T) {
}
for _, tc := range tests {
info := &ResourceInfo{}
populateNodeInfo(tc.Ingress, info)
populateNodeInfo(tc.Ingress, info, []string{})
assert.Equal(t, 0, len(info.Info))
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
Expand All @@ -388,7 +388,7 @@ func TestGetIngressInfo(t *testing.T) {

func TestGetLinkAnnotatedIngressInfo(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testLinkAnnotatedIngress, info)
populateNodeInfo(testLinkAnnotatedIngress, info, []string{})
assert.Equal(t, 0, len(info.Info))
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
Expand All @@ -412,7 +412,7 @@ func TestGetLinkAnnotatedIngressInfo(t *testing.T) {

func TestGetIngressInfoWildCardPath(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testIngressWildCardPath, info)
populateNodeInfo(testIngressWildCardPath, info, []string{})
assert.Equal(t, 0, len(info.Info))
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
Expand All @@ -436,7 +436,7 @@ func TestGetIngressInfoWildCardPath(t *testing.T) {

func TestGetIngressInfoWithoutTls(t *testing.T) {
info := &ResourceInfo{}
populateNodeInfo(testIngressWithoutTls, info)
populateNodeInfo(testIngressWithoutTls, info, []string{})
assert.Equal(t, 0, len(info.Info))
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
Expand Down Expand Up @@ -481,7 +481,7 @@ func TestGetIngressInfoWithHost(t *testing.T) {
- ip: 107.178.210.11`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
Expand Down Expand Up @@ -514,7 +514,7 @@ func TestGetIngressInfoNoHost(t *testing.T) {
`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
TargetRefs: []v1alpha1.ResourceRef{{
Expand Down Expand Up @@ -549,7 +549,7 @@ func TestExternalUrlWithSubPath(t *testing.T) {
- ip: 107.178.210.11`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"}
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
Expand Down Expand Up @@ -585,7 +585,7 @@ func TestExternalUrlWithMultipleSubPaths(t *testing.T) {
- ip: 107.178.210.11`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

expectedExternalUrls := []string{"https://helm-guestbook.com/my/sub/path/", "https://helm-guestbook.com/my/sub/path/2", "https://helm-guestbook.com"}
actualURLs := info.NetworkingInfo.ExternalURLs
Expand Down Expand Up @@ -615,7 +615,7 @@ func TestExternalUrlWithNoSubPath(t *testing.T) {
- ip: 107.178.210.11`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

expectedExternalUrls := []string{"https://107.178.210.11"}
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
Expand Down Expand Up @@ -643,8 +643,54 @@ func TestExternalUrlWithNetworkingApi(t *testing.T) {
- ip: 107.178.210.11`)

info := &ResourceInfo{}
populateNodeInfo(ingress, info)
populateNodeInfo(ingress, info, []string{})

expectedExternalUrls := []string{"https://107.178.210.11"}
assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
}

func TestCustomLabel(t *testing.T) {
configmap := strToUnstructured(`
apiVersion: v1
kind: ConfigMap
metadata:
name: cm`)

info := &ResourceInfo{}
populateNodeInfo(configmap, info, []string{"my-label"})

assert.Equal(t, 0, len(info.Info))

configmap = strToUnstructured(`
apiVersion: v1
kind: ConfigMap
metadata:
name: cm
labels:
my-label: value`)

info = &ResourceInfo{}
populateNodeInfo(configmap, info, []string{"my-label", "other-label"})

assert.Equal(t, 1, len(info.Info))
assert.Equal(t, "my-label", info.Info[0].Name)
assert.Equal(t, "value", info.Info[0].Value)

configmap = strToUnstructured(`
apiVersion: v1
kind: ConfigMap
metadata:
name: cm
labels:
my-label: value
other-label: value2`)

info = &ResourceInfo{}
populateNodeInfo(configmap, info, []string{"my-label", "other-label"})

assert.Equal(t, 2, len(info.Info))
assert.Equal(t, "my-label", info.Info[0].Name)
assert.Equal(t, "value", info.Info[0].Value)
assert.Equal(t, "other-label", info.Info[1].Name)
assert.Equal(t, "value2", info.Info[1].Value)
}
3 changes: 3 additions & 0 deletions docs/operator-manual/argocd-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ data:
clusters:
- "*.local"
# An optional comma-separated list of metadata.labels to observe in the UI.
resource.customLabels: tier

resource.compareoptions: |
# if ignoreAggregatedRoles set to true then differences caused by aggregated roles in RBAC resources are ignored.
ignoreAggregatedRoles: true
Expand Down
4 changes: 4 additions & 0 deletions docs/operator-manual/declarative-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,10 @@ Notes:
* Invalid globs result in the whole rule being ignored.
* If you add a rule that matches existing resources, these will appear in the interface as `OutOfSync`.

## Resource Custom Labels

Custom Labels configured with `resource.customLabels` (comma separated string) will be displayed in the UI (for any resource that defines them).

## SSO & RBAC

* SSO configuration details: [SSO](./user-management/index.md)
Expand Down
14 changes: 14 additions & 0 deletions util/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ const (
resourceExclusionsKey = "resource.exclusions"
// resourceInclusions is the key to the list of explicitly watched resources
resourceInclusionsKey = "resource.inclusions"
// resourceCustomLabelKey is the key to a custom label to show in node info, if present
resourceCustomLabelsKey = "resource.customLabels"
// configManagementPluginsKey is the key to the list of config management plugins
configManagementPluginsKey = "configManagementPlugins"
// kustomizeBuildOptionsKey is a string of kustomize build parameters
Expand Down Expand Up @@ -1885,3 +1887,15 @@ func (mgr *SettingsManager) GetGlobalProjectsSettings() ([]GlobalProjectSettings
func (mgr *SettingsManager) GetNamespace() string {
return mgr.namespace
}

func (mgr *SettingsManager) GetResourceCustomLabels() ([]string, error) {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return []string{}, fmt.Errorf("failed getting configmap: %v", err)
}
labels := argoCDCM.Data[resourceCustomLabelsKey]
if labels != "" {
return strings.Split(labels, ","), nil
}
return []string{}, nil
}

0 comments on commit 2d18404

Please sign in to comment.