crossplane-contrib / function-go-templating

A Go templating composition function
https://crossplane.io
Apache License 2.0
53 stars 35 forks source link

Help: Error when using index function inside a range loop #114

Closed ale9412 closed 1 month ago

ale9412 commented 1 month ago

Crossplane version 1.15.1 function-go-tem,plate version: v0.5.0 openstack-provider version: v0.3.0

I am new using this function, I mainly using it for creating openstack resources. I am trying to create a composition with a range loop, that will iterate over a variable provided by the composite resource. However there is no way that I can make the index function work inside the range loop. XR

apiVersion: crossplanedemo.example.org/v1alpha1
kind: xVirtualMachine
metadata:
  name: custom-vm  
spec:
  nodeCount: 2

Composition

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: openstackvm
spec:
  compositeTypeRef:
    apiVersion: crossplanedemo.example.org/v1alpha1
    kind: xVirtualMachine
  mode: Pipeline
  pipeline:
    - step: create-vm
      functionRef:
        name: function-go-templating
      input:
        apiVersion: gotemplating.fn.crossplane.io/v1beta1
        kind: GoTemplate
        source: Inline
        inline:
          template: |
            apiVersion: compute.openstack.crossplane.io/v1alpha1
            kind: KeypairV2
            metadata:
              annotations:
                gotemplating.fn.crossplane.io/composition-resource-name: keypair
              name: crossplane-demo-keypair
            spec:
              providerConfigRef:
                name: provider-openstack
              forProvider:
                name: crossplane-demo-keypair
              writeConnectionSecretToRef:
                namespace: crossplane-system
                name: crossplane-demo-keypair-secret
            {{- range $i := until ( .observed.composite.resource.spec.nodeCount | int ) }}
            ---
            apiVersion: compute.openstack.crossplane.io/v1alpha1
            kind: InstanceV2
            metadata:
              name: crossplane-instance-{{ $i }}
              annotations:
                gotemplating.fn.crossplane.io/composition-resource-name: crossplane-instance-{{ $i }}
                {{ setResourceNameAnnotation (print "crossplane-instance-" $i) }}
            spec:
              publishConnectionDetailsTo:
                name: crossplane-demo-vm-conn-secret
              forProvider:
                name: "crossplane-demo-instance-{{ $i }}"
                flavorName: "public.4c.8r.0d"
                imageName: ubuntu-18.04-server-cloudimg-20220325
                keyPair: {{ $.observed.resources.keypair.resource.status.atProvider.name }}
                network: 
                  - name: "admin_10.168.99_app"
                availabilityZone: "nova"
                securityGroups: [default, ssh, icmp]
              providerConfigRef:
                name: provider-openstack
            ---
            apiVersion: blockstorage.openstack.crossplane.io/v1alpha1
            kind: VolumeV3
            metadata:
              annotations:
                gotemplating.fn.crossplane.io/composition-resource-name: crossplane-volume-{{ $i }}
                {{ setResourceNameAnnotation (print "volume-" $i) }}
              labels:
                testing.upbound.io/example-name: crossplane-demo-volume
              name: crossplane-volume-{{ $i }}
            spec:
              providerConfigRef:
                name: provider-openstack
              forProvider:
                description: Created by crossplane
                name: crossplane-volume-{{ $i }}
                size: 20
            {{- end }}
    - step: attach-volumes
      functionRef:
        name: function-go-templating
      input:
        apiVersion: gotemplating.fn.crossplane.io/v1beta1
        kind: GoTemplate
        source: Inline
        inline:
          template: |
            {{- range $i := until ( .observed.composite.resource.spec.nodeCount | int ) }}
            # Attach Volume
            apiVersion: compute.openstack.crossplane.io/v1alpha1
            kind: VolumeAttachV2
            metadata:
              name: crossplane-demo-va-{{ $i }}
              annotations:
                gotemplating.fn.crossplane.io/composition-resource-name: volume-attach-{{ $i }}
            spec:
              instanceId: {{ (index $.observed.resources (print "crossplane-instance-" $i)).resource.status.atProvider.id }}
              volumeId: {{ (index $.observed.resources (print "crossplane-volume-" $i)).resource.status.atProvider.id }}
            {{- end }}
    - step: automatically-detect-readiness
      functionRef:
        name: function-auto-ready

I had assumed that with this config. The keypair will be created then the VMs and volumes and for last th Volumes Attach resources, but XR complains that the index is nil: cannot compose resources: pipeline step "attach-volumes" returned a fatal result: cannot execute template: template: manifests:10:18: executing "manifests" at <index $.observed.resources (print "crossplane-instance-" $i)>: error calling index: index of untyped nil I understand that this error occurs when the resource does not exist yet, hence there is not observed.resources yet, but what I expect is that the resources get created in the order describe above and eventually the index stop being nil and can actually be called using the resource name.

If i remove the step attach-volume, everything is deployed successfully What Am i doing wrong? What is the correct way of setting the index in order to work. Thanks for your time

fernandezcuesta commented 1 month ago

You might notice that during the first reconcile loops the observed resources might be empty. Anyway I guess it's safer to use the helper getComposedResource and wrap altogether in an if/with clause.

bobh66 commented 1 month ago

You can use the dig function to look for index keys that may or may not be there yet and return a default value if they aren't.

It can be problematic to use index values for resource names and composition-resource-names because if the list ever changes size the resources can reshuffle and delete/recreate unintentionally. It's usually better to use some unique attribute or combination of attributes of the resource you are creating for the name so you know it will never change.

ale9412 commented 1 month ago

The dig suggestion worked perfectly Something like this did the job: {{ dig "resources" (print "crossplane-instance-" $i) "resource" "status" "atProvider" "id" $.observed }} Thanks very much