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

Add templateValues to HelmApp, Bundle, etc #3267

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Danil-Grigorev
Copy link
Contributor

Refers to #3266

Implement new field inside Helm options: templateValues, which performs templating of each individual value before converting data to yaml. This allows to use more complex template expressions then currently available to template string data, like creation of maps and lists.

@Danil-Grigorev Danil-Grigorev requested a review from a team as a code owner January 28, 2025 13:01
@Danil-Grigorev Danil-Grigorev force-pushed the native-template-value-support branch 2 times, most recently from 21e3d46 to 5dbcbc0 Compare January 28, 2025 15:07
@Danil-Grigorev
Copy link
Contributor Author

With this change I was able to deploy helm chart with proper modifications:

apiversion: fleet.cattle.io/v1alpha1
kind: HelmApp
metadata:
  finalizers:
  - fleet.cattle.io/helmapp-finalizer
  generation: 3
  name: sample1
  namespace: default
  resourceVersion: "23694"
  uid: 511580b5-733a-4304-9cbb-229448989a3a
spec:
  helm:
    chart: tigera-operator
    releaseName: projectcalico
    repo: https://docs.tigera.io/calico/charts
    templateValues:
      installation: |-
        cni:
          type: Calico
          ipam:
            type: HostLocal
        calicoNetwork:
          bgp: Disabled
          mtu: 1350
          ipPools:
            ${- range $cidr := .ClusterValues.Cluster.spec.clusterNetwork.pods.cidrBlocks }
            - cidr: "${ $cidr }"
              encapsulation: None
              natOutgoing: Enabled
              nodeSelector: all()${- end}
  ignore: {}
  insecureSkipTLSVerify: true
  targets:
  - clusterName: docker-demo

created correct BundleDeployment:

apiversion: fleet.cattle.io/v1alpha1
kind: BundleDeployment
metadata:
  creationTimestamp: "2025-01-28T15:00:20Z"
  finalizers:
  - fleet.cattle.io/bundle-deployment-finalizer
  generation: 1
  labels:
    fleet.cattle.io/bundle-name: sample1
    fleet.cattle.io/bundle-namespace: default
    fleet.cattle.io/cluster: docker-demo
    fleet.cattle.io/cluster-namespace: default
    fleet.cattle.io/fleet-helm-name: sample1
    fleet.cattle.io/managed: "true"
  name: sample1
  namespace: cluster-default-docker-demo-9a1d57cc2ad8
  resourceVersion: "22820"
  uid: 37600a86-c9e4-4445-a757-5611c5d27ace
spec:
  deploymentID: :fb788a089566f35d87c7d4b2ed4a499a2d3e36315b44cfa9446f66996b6a7c82
  helmChartOptions:
    helmAppInsecureSkipTLSVerify: true
  options:
    helm:
      chart: tigera-operator
      releaseName: projectcalico
      repo: https://docs.tigera.io/calico/charts
      templateValues:
        installation: |-
          cni:
            type: Calico
            ipam:
              type: HostLocal
          calicoNetwork:
            bgp: Disabled
            mtu: 1350
            ipPools:
              ${- range $cidr := .ClusterValues.Cluster.spec.clusterNetwork.pods.cidrBlocks }
              - cidr: "${ $cidr }"
                encapsulation: None
                natOutgoing: Enabled
                nodeSelector: all()${- end}
      values:
        installation:
          calicoNetwork:
            bgp: Disabled
            ipPools:
            - cidr: 10.1.0.0/16
              encapsulation: None
              natOutgoing: Enabled
              nodeSelector: all()
            mtu: 1350
          cni:
            ipam:
              type: HostLocal
            type: Calico
      version: v3.29.1
    ignore: {}
  stagedDeploymentID: :fb788a089566f35d87c7d4b2ed4a499a2d3e36315b44cfa9446f66996b6a7c82

@Danil-Grigorev Danil-Grigorev changed the title Add templateValues to HelmApp, Bundle, etc Add templateValues to HelmApp, Bundle, etc Jan 28, 2025
@kkaempf kkaempf added this to the v2.11.0 milestone Jan 29, 2025
Copy link
Contributor

@weyfonk weyfonk left a comment

Choose a reason for hiding this comment

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

I like the idea, but I wonder if we really need both values and templateValues; perhaps we could template each member of the existing value field before serialising it to YAML, WDYT?

Comment on lines +191 to +193
if opts.Helm.Values.Data == nil {
opts.Helm.Values.Data = 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.

Why do we still need this block? My understanding is that cmp.Or would populate the Data field if needed 🤔

opts.Helm.Values.Data = map[string]interface{}{}
}

if opts.Helm.TemplateValues == nil && (opts.Helm.Values == nil || opts.Helm.Values.Data == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we've ensured that opts.Helm.Values.Data exists, wouldn't the following be enough?

Suggested change
if opts.Helm.TemplateValues == nil && (opts.Helm.Values == nil || opts.Helm.Values.Data == nil) {
if opts.Helm.TemplateValues == nil && opts.Helm.Values.Data == nil {

return err
}

maps.Copy(opts.Helm.Values.Data, templatedData)
Copy link
Contributor

Choose a reason for hiding this comment

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

This effectively means that, if both a value and a template value exist with the same key, the latter will overwrite the former. Not a problem, but something we'll want to document, and ideally test :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the doc message on the field. Not sure we need to test this, since the method is a part of standard lib, but added a scenario

var value interface{}
err = kyaml.Unmarshal(b.Bytes(), &value)
if err != nil {
return nil, fmt.Errorf("failed to interpret rendered template as helm values: %#v, %v", renderedValues, 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 output the whole set of rendered values here? Is the rationale to enable the user to understand where successful processing stopped? Otherwise, how about outputting b.String() (or rather a string conversion of the previous b.Bytes())?

@@ -314,6 +329,70 @@ func TestProcessTemplateValues(t *testing.T) {
t.Fatal("join func was not right")
}

templatedValuesData, err := processTemplateValuesData(bundle.Helm.TemplateValues, values)
if err != nil {
t.Fatalf("error during label processing %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

🤨 Not sure why that error message is phrased this way (same for processTemplateValues)

Suggested change
t.Fatalf("error during label processing %v", err)
t.Fatalf("error during templated values processing %v", err)

@Danil-Grigorev
Copy link
Contributor Author

Danil-Grigorev commented Jan 29, 2025

I like the idea, but I wonder if we really need both values and templateValues; perhaps we could template each member of the existing value field before serialising it to YAML, WDYT?

I also thought about it, but that has the same problem as previous solution. As the value in the map is interface{}, what type it actually contains is unknown. Template processing requires string data, which is only achievable after serialization - so it has to go through yaml decoder. These template expressions do not render as valid yaml, so we can't do anything.

Even if the interface is initially checked to be string, and we can skip yaml pre-processing, it is unclear if the templated result needs to stay a sting, or it has to be a map/list. templatedValues makes an assumption that the string inside will contain raw data (map/list). So I made a field addition to make it distinct in order to avoid breaking changes in API.

Maybe there is a way around the issue I'm not seeing here.

@Danil-Grigorev Danil-Grigorev force-pushed the native-template-value-support branch 3 times, most recently from 126b010 to f308721 Compare January 30, 2025 10:50
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
@Danil-Grigorev Danil-Grigorev force-pushed the native-template-value-support branch from f308721 to 86be9f3 Compare January 30, 2025 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

3 participants