Skip to content

Commit fa7589f

Browse files
Add lookupClusterClaim
This new function has basically the same functionality as using the `lookup` function, in particular it does not stop the templating with an error if the specified ClusterClaim does not exist. This is especially helpful for ClusterClaims (in comparison to the analogous functions for ConfigMaps and Secrets) because the apiVersion for ClusterClaims is quite long and error-prone to read/write. Refs: - https://issues.redhat.com/browse/ACM-18626 Signed-off-by: Justin Kulikauskas <jkulikau@redhat.com>
1 parent 1482826 commit fa7589f

File tree

5 files changed

+67
-5
lines changed

5 files changed

+67
-5
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ Function | Description | Example
4242
`base64enc` | Decodes the input Base64 string to its decoded form. |`{{ "VGVtcGxhdGVzIHJvY2shCg==" \| base64dec }}`
4343
`base64enc` | Encodes an input string in the Base64 format. | `{{ "Templating rocks!" \| base64enc }}`
4444
`indent` | Indents the input string by the specified amount. | `{{ "Templating\nrocks!" \| indent 4 }}`
45-
`fromClusterClaim` | Returns the value of a specific `ClusterClaim`. | `{{ fromClusterClaim "name" }}`
46-
`fromConfigMap` | Returns the value of a key inside a `ConfigMap`. | `{{ fromConfigMap "namespace" "config-map-name" "key" }}`
45+
`fromClusterClaim` | Returns the value of a specific `ClusterClaim`. Errors if the `ClusterClaim` is not found. | `{{ fromClusterClaim "name" }}`
46+
`lookupClusterClaim` | Returns the value of a specific `ClusterClaim`. Returns an empty string if the `ClusterClaim` is not found. | `{{ lookupClusterClaim "name" }}`
47+
`fromConfigMap` | Returns the value of a key inside a `ConfigMap`. Errors if the `ConfigMap` is not found. | `{{ fromConfigMap "namespace" "config-map-name" "key" }}`
4748
`copyConfigMapData` | Returns the `data` contents of the specified `ConfigMap` | `{{ copyConfigMapData "namespace" "config-map-name" }}`
48-
`fromSecret` | Returns the value of a key inside a `Secret`. If the `EncryptionMode` is set to `EncryptionEnabled`, this will return an encrypted value. | `{{ fromSecret "namespace" "secret-name" "key" }}`
49+
`fromSecret` | Returns the value of a key inside a `Secret`. If the `EncryptionMode` is set to `EncryptionEnabled`, this will return an encrypted value. Errors if the `Secret` is not found. | `{{ fromSecret "namespace" "secret-name" "key" }}`
4950
`copySecretData` | Returns the `data` contents of the specified `Secret`. If the `EncryptionMode` is set to `EncryptionEnabled`, this will return an encrypted value. | `{{ copySecretData "namespace" "secret-name" }}`
50-
`lookup` | Generic lookup function for any Kubernetes object. | `{{ (lookup "v1" "Secret" "namespace" "name").data.key }}`
51+
`lookup` | Generic lookup function for any Kubernetes object. Returns an empty string if the resource is not found. | `{{ (lookup "v1" "Secret" "namespace" "name").data.key }}`
5152
`protect` | Encrypts any string using AES-CBC. | `{{ "super-secret" \| protect }}`
5253
`toBool` | Parses an input boolean string converts it to a boolean but also removes any quotes around the map value. | `key: "{{ "true" \| toBool }}"` => `key: true`
5354
`toInt` | Parses an input string and returns an integer but also removes anyquotes around the map value. | `key: "{{ "6" \| toInt }}"` => `key: 6`

pkg/templates/clusterconfig_funcs.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package templates
66
import (
77
"errors"
88
"fmt"
9+
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
911
)
1012

1113
const clusterClaimAPIVersion string = "cluster.open-cluster-management.io/v1alpha1"
@@ -40,3 +42,37 @@ func (t *TemplateResolver) fromClusterClaim(options *ResolveOptions, claimName s
4042

4143
return value, nil
4244
}
45+
46+
func (t *TemplateResolver) lookupClusterClaimHelper(options *ResolveOptions) func(string) (string, error) {
47+
return func(claimName string) (string, error) {
48+
return t.lookupClusterClaim(options, claimName)
49+
}
50+
}
51+
52+
func (t *TemplateResolver) lookupClusterClaim(options *ResolveOptions, claimName string) (string, error) {
53+
if claimName == "" {
54+
return "", errors.New("a claim name must be provided")
55+
}
56+
57+
clusterClaim, err := t.getOrList(options, nil, clusterClaimAPIVersion, "ClusterClaim", "", claimName)
58+
if err != nil {
59+
if apierrors.IsNotFound(err) {
60+
return "", nil
61+
}
62+
63+
return "", err
64+
}
65+
66+
spec, ok := clusterClaim["spec"].(map[string]interface{})
67+
if !ok {
68+
return "", fmt.Errorf("unexpected ClusterClaim format: %s", claimName)
69+
}
70+
71+
var value string
72+
73+
if _, ok := spec["value"]; ok {
74+
value = spec["value"].(string)
75+
}
76+
77+
return value, nil
78+
}

pkg/templates/clusterconfig_funcs_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,26 @@ func TestFromClusterClaimNotFound(t *testing.T) {
3030

3131
expectedMsg := `clusterclaims.cluster.open-cluster-management.io "something-nonexistent" not found`
3232
if err == nil || err.Error() != expectedMsg {
33-
t.Fatalf("Expected an error for the missing claim name but got %v", err)
33+
t.Fatalf("Expected an error for the missing claim but got %v", err)
3434
}
3535

3636
if rv != "" {
3737
t.Fatalf("Expected no return value due to the error but got %v", rv)
3838
}
3939
}
40+
41+
func TestLookupClusterClaimNotFound(t *testing.T) {
42+
resolver, err := NewResolver(k8sConfig, Config{})
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
47+
rv, err := resolver.lookupClusterClaim(&ResolveOptions{}, "something-nonexistent")
48+
if err != nil {
49+
t.Fatalf("Expected no error for the missing claim, but got %v", err)
50+
}
51+
52+
if rv != "" {
53+
t.Fatalf("Expected empty return value but got %v", rv)
54+
}
55+
}

pkg/templates/templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ func (t *TemplateResolver) ResolveTemplate(
613613
"fromSecret": t.fromSecretHelper(options, &resolvedResult),
614614
"fromConfigMap": t.fromConfigMapHelper(options),
615615
"fromClusterClaim": t.fromClusterClaimHelper(options),
616+
"lookupClusterClaim": t.lookupClusterClaimHelper(options),
616617
"getNodesWithExactRoles": t.getNodesWithExactRolesHelper(options, &resolvedResult),
617618
"hasNodesWithExactRoles": t.hasNodesWithExactRolesHelper(options),
618619
"lookup": t.lookupHelper(options, &resolvedResult),

pkg/templates/templates_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,14 @@ func TestResolveTemplateDefaultConfig(t *testing.T) {
677677
inputTmpl: `value: '{{ fromClusterClaim "env" }}'`,
678678
expectedResult: "value: dev",
679679
},
680+
"lookupClusterClaim": {
681+
inputTmpl: `value: '{{ lookupClusterClaim "env" }}'`,
682+
expectedResult: "value: dev",
683+
},
684+
"lookupClusterClaim_NonExist": {
685+
inputTmpl: `value: '{{ lookupClusterClaim "nonexist" }}'`,
686+
expectedResult: `value: ""`,
687+
},
680688
"lookup_duplicate_list_uses_resolve_cache": {
681689
inputTmpl: `data: '{{ (index (lookup "v1" "ConfigMap" "testns" "" "env=a").items 0).data.cmkey1 }}` +
682690
`{{ (index (lookup "v1" "ConfigMap" "testns" "" "env=a").items 0).data.cmkey1 }}'`,

0 commit comments

Comments
 (0)