Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow helm values overrides to inject cluster labels via go templates #507

Closed

Conversation

gregsidelinger
Copy link

Signed-off-by: Greg Sidelinger gate@ilive4code.net

Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
@mattmattox
Copy link

This would be really nice for when you want to expose a service on all clusters.
Example: longhorn.ClusterName.example.com

Currently, you can use variables like global.fleet.clusterLabels.management.cattle.io/cluster-display-name in your values but you can't append anything to the variable.

@@ -72,6 +72,10 @@ helm:
# All labels on Rancher clusters are available using global.fleet.clusterLabels.LABELNAME
# These can now be accessed directly as variables
variableName: global.fleet.clusterLabels.LABELNAME
# They can also be access using go template logic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small grammatical fixes:

In addition, they can be accessed using Go template logic

Comment on lines 110 to 135
templateName, ok := bundle.Helm.Values.Data["templateName"]
if !ok {
t.Fatal("key templateName not found")
}

if templateName != "kubernetes.io/cluster/local" {
t.Fatal("unable to assert correct template")
}

templateMissing, ok := bundle.Helm.Values.Data["templateMissing"]
if !ok {
t.Fatal("key templateMissing not found")
}

if templateMissing != "{{ .missingValue }}" {
t.Fatal("unable to assert correct templateMising")
}

templateLogic, ok := bundle.Helm.Values.Data["templateLogic"]
if !ok {
t.Fatal("key templateLogic not found")
}

if templateLogic != "Non Prod" {
t.Fatal("unable to assert correct templateLogic")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's remove blank lines between the same template comparisons

@@ -520,6 +529,18 @@ func processLabelValues(valuesMap map[string]interface{}, clusterLabels map[stri
} else {
return fmt.Errorf("invalid_label_reference %s in key %s", valStr, key)
}
} else {
switch val.(type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already gather valStr on line 523. Perhaps, we can refactor this code to use it instead. I'd prefer that we handle this scenario once.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually have an updated PR I was going to send in that changes how a lot of this stuff is done and does not have any extra string conversions in that area of the code.

After playing around with the configmap stuff in 0.3.6 I thought it would be fun to allow the value substitution to come from any overridden values like the ones imported from a configmap, not just the ones in helm.vlaues from the fleet.yaml. So I moved this code into the helm install function to do that. I also added basic cluster annotation support on top of the cluster one to deal with values that can not be stored in an label. It's a bigger PR but adds a lot more flexibility that has been preventing us from using fleet for more deployments.

@gregsidelinger
Copy link
Author

#528 supersedes this PR

@nickgerace
Copy link
Contributor

@gregsidelinger why are these PRs separate? I'd personally prefer one PR with discrete commits (or squashed, if small enough) in order to reduce context switching. However, it's probable that there's a perspective here that I am missing.

@gregsidelinger
Copy link
Author

@nickgerace Because I'm not 100% sure if there are any major design issues with where/how I did things in #528. It was a much bigger change than this small PR which has more lines of testing than actual code. I would say look at #528 and if nothing major jumps out with the approach I will close this one and focus on it.

Cleanup extra string convirsion
Cleanup extra whitespace in tests

Signed-off-by: Greg Sidelinger <gate@ilive4code.net>
@nickgerace
Copy link
Contributor

Thanks @gregsidelinger for the clarification. I'll pause my review here and jump to the other one.

@gregsidelinger
Copy link
Author

So after testing this branch and the one in #528 I think this is the PR that should be merged. I updated this PR with annotation support and added a few more test cases. As well as trapping errors from calling the go-templating that causes panics. Docs could get some for TLC as accessing labels and annotations with dashes in their name requires using index which I added a test case for. But I'm not sure if all of this should end up in the github-structure docs as that is starting to look full and out of place if there are too many examples of using go tempaltes.

The only real difference between this one and #528 is this supports only fleet.yaml values and valuesfiles. Configmaps and secrets in the destination cluster are not processed, which may have been overkill.

@xMAC94x
Copy link

xMAC94x commented Nov 17, 2021

Hi @gregsidelinger thanks for this PR, its exactly what i am looking for. I noticed there is a comment on your PR that need to be resolved before merging.
Fyi, i found out that even Yaml Anchors don't work in current release fleet version ( https://helm.sh/docs/chart_template_guide/yaml_techniques/#yaml-anchors )

Copy link
Contributor

@aiyengar2 aiyengar2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your PR @gregsidelinger! Apologies for the late re-review, had some comments on this approach though.

Comment on lines +76 to +77
variableName: "kubernetes.io/cluster/{{ .global.fleet.clusterLabels.LABELNAME }}"
variableName: "{{ if eq .global.fleet.clusterLabels.LABELNAME \"production\" }}Production Workload{{ else }}Non Prod{{ end }}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the values provided here are intended to be Go templates that are parsed by the Helm chart? For example, values like this one found in kube-prometheus-stack that is expected to be a Go template supplied to the chart, as seen by the tpl call made here

Based on this approach, we would no longer be able to support Go template encoded strings provided via the values.yaml to be interpreted by Helm instead of Fleet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an excellent point. I would think we could make this behavior opt in. So something like add parseValuesValues: true (that's a bad name fyi). Then if somebody wants to templatize something that has templates in it they would escape the template characters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is true. An early version of the code actually would leave any lines alone that returned an error from the go templating call allowing fleet to still handle values files with go templating strings in them. This however would not return errors to the user if they did something like this variableName: "kubernetes.io/cluster/{{ .global.fleet.clusterLabels.MISSING_LABEL }}" so I changed it as I forgot why I did it in the first place till you mentioned it.

Assuming I leave all clusterLabels prefixed with .global.fleet. a simple approach would be to only call to go templating if the string contained {{, }} and .global.fleet. which should in theory be safe. for most use cases. Or only support the templating on stuff in fleet.yaml and not values files.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of helm.preProcessValues as the setting name to turn the templating on. I actually submitted a patch to @rajiteh for his PR #671 instead of more updates to my code. Granted I do have all the changes requested for this PR sitting locally but I think his approach is better you are should all review his PR.

func processValues(valuesMap map[string]interface{}, clusterLabels map[string]string, clusterAnnotations map[string]string) (err error) {
labelPrefix := "global.fleet.clusterLabels."
annotationPrefix := "global.fleet.clusterAnnotations."
scopedClusterLables := map[string]interface{}{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
scopedClusterLables := map[string]interface{}{
scopedClusterLabels := map[string]interface{}{

nit: incorrect spelling

return fmt.Errorf("invalid_label_reference %s in key %s", valStr, key)
}
} else if strings.HasPrefix(valStr, annotationPrefix) {
annoation := strings.TrimPrefix(valStr, annotationPrefix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
annoation := strings.TrimPrefix(valStr, annotationPrefix)
annotation := strings.TrimPrefix(valStr, annotationPrefix)

nit: typo

}
} else if strings.HasPrefix(valStr, annotationPrefix) {
annoation := strings.TrimPrefix(valStr, annotationPrefix)
annoationVal, annoationPresent := clusterLabels[annoation]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
annoationVal, annoationPresent := clusterLabels[annoation]
annotationVal, annotationPresent := clusterLabels[annotation]

nit: typo

pkg/target/target.go Show resolved Hide resolved
Comment on lines +549 to +553
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Failed to process template substitution for key '%s' with value '%s': [%v]", key, valStr, err)
}
}()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this? Is there a part of our code that panics?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, a bad go template can actually raise a panic.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the panic introduced with this change or is it already in master and you just added the logging ?

Comment on lines +141 to +151
bundle.Helm.Values.Data["error"] = "{{ .missingValue }}"
err = processValues(bundle.Helm.Values.Data, clusterLabels, clusterAnnotations)
if err == nil {
t.Fatalf("failed to error on {{ .missingValue }}")
}

bundle.Helm.Values.Data["error"] = "{{ .missingValue-panic }}"
err = processValues(bundle.Helm.Values.Data, clusterLabels, clusterAnnotations)
if err == nil {
t.Fatalf("failed to error on {{ .missingValue-panic }}")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are these different tests?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is designed to raise a panic from the go templating. I ran into a panic at one point in time so I wrote a test for it. If I recall correctly the - in the variable name actually causes the issue.

@gregsidelinger
Copy link
Author

@xMAC94x @ibuildthecloud @aiyengar2
Design question. I spend some time over the weekend adding a boolean to turn on the templating to deal with charts that support the templating this would break and fixing the other issues pointed out. I also spend some time on PR #671 too and I like @rajiteh approach better. The flexibility of adding templateContext to the clusters seems better than relying upon labels and annotations to inject all data. And personally I like his format for accessing values inside the templating instead of the .global.fleet prefix I used.

	values := map[string]interface{}{
		"ClusterNamespace":   cluster.Namespace,
		"ClusterName":        cluster.Name,
		"ClusterLabels":      clusterLabels,
		"ClusterAnnotations": clusterAnnotations,
		"Values":             templateContext,
	}

So from a long term design perspective which approach is preferred?

@xMAC94x
Copy link

xMAC94x commented Feb 15, 2022

Agree, a special syntax is prob a better solution than put everything in labels, this would also get around of some restrictions as label values cannot contain all UTF-8 characters, right ?

@prachidamle
Copy link
Member

prachidamle commented Mar 18, 2022

@gregsidelinger Thank you for this PR and also the follow up design questions. I took a look at this PR and the conversations before where the problem of "how do we support templating for the charts that already have Go templates to be parsed by Helm engine". I see that you tried out the setting "helm.preProcessValues" method to turn on the Fleet templating when required.

So I think when the setting "helm.preProcessValues" is "true" it would indicate that Fleet needs to parse the templated values before passing down to Helm. Is that correct?

How would this make Fleet ignore processing of the templated charts intended to be processed by Helm engine? Are for such charts the setting should always be off?

I will go over the second PR #671 to understand the second approach and raise any questions I face.

@rajiteh
Copy link
Contributor

rajiteh commented Mar 23, 2022

@prachidamle author of PR #671 here. I have adopted the same config except inversed the config key. Essentially the config value and its default is helm.disablePreprocess: false, so the user could override it to true to disable templating. This will be particularly useful if they're sending gotpls as values which is the case for things like prometheus alertrules and etc.

However, a user could still utilize fleet-level helm templates and retain downstream templating ability by wrapping it in an escape block {{`{{ escaped_for_downstream }}`}}

@prachidamle
Copy link
Member

@rajiteh so essentially when the gotpls are wrapped in an escape block {{{{ escaped_for_downstream }}}}, the fleet template parsing will unescape it for further parsing via helm?

@rajiteh
Copy link
Contributor

rajiteh commented Mar 24, 2022

Yes. The template string {{`{{ hello }}`}} will get templated out by gotpl (which is what the proposed solutions are utilizing) as {{ hello }}

https://go.dev/play/p/VvaLfCXnyhd

@SheilaghM
Copy link

@gregsidelinger @rajiteh - are we good to close this PR in favor of #671 and pursue that avenue?

@gregsidelinger
Copy link
Author

@gregsidelinger @rajiteh - are we good to close this PR in favor of #671 and pursue that avenue?

I'm fine with focusing on #671. I did submit a PR to @rajiteh over the weekend to address the things brought up so far in the review so hopefully he is ok to merge those changes in.

@SheilaghM
Copy link

Thanks @gregsidelinger. I'm going to close this ticket then so we focus on #671.

@SheilaghM SheilaghM closed this Mar 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants