@@ -714,6 +714,176 @@ spec:
714714 })
715715 })
716716
717+ Context ("existing Go template syntax escaping" , func () {
718+ It ("should escape existing Go template syntax in CRD samples" , func () {
719+ crdResource := & unstructured.Unstructured {}
720+ crdResource .SetAPIVersion ("apiextensions.k8s.io/v1" )
721+ crdResource .SetKind ("CustomResourceDefinition" )
722+ crdResource .SetName ("changetransferpolicies.promoter.argoproj.io" )
723+
724+ content := `apiVersion: apiextensions.k8s.io/v1
725+ kind: CustomResourceDefinition
726+ metadata:
727+ name: changetransferpolicies.promoter.argoproj.io
728+ spec:
729+ names:
730+ kind: ChangeTransferPolicy
731+ versions:
732+ - name: v1alpha1
733+ schema:
734+ openAPIV3Schema:
735+ properties:
736+ spec:
737+ properties:
738+ pullRequest:
739+ properties:
740+ template:
741+ properties:
742+ description:
743+ default: "Promoting {{ .ChangeTransferPolicy.Spec.ActiveBranch }}"
744+ type: string
745+ title:
746+ default: "Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}"
747+ type: string`
748+
749+ result := templater .ApplyHelmSubstitutions (content , crdResource )
750+
751+ // Existing {{ }} should be escaped to {{ "{{ ... }}" }}
752+ Expect (result ).To (ContainSubstring (`{{ "{{ .ChangeTransferPolicy.Spec.ActiveBranch }}" }}` ),
753+ "existing template syntax should be escaped" )
754+ Expect (result ).To (ContainSubstring (`{{ "{{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}" }}` ),
755+ "function calls in templates should be escaped" )
756+
757+ // Should NOT have unescaped Go template syntax (which would break Helm)
758+ // We check that all ChangeTransferPolicy references are properly wrapped
759+ // Pattern checks for: default: "...<text>{{ .ChangeTransferPolicy" (not escaped)
760+ // The properly escaped version is: default: "...{{ "{{ .ChangeTransferPolicy..." }}"
761+ Expect (result ).NotTo (MatchRegexp (`default:\s+"[^{]*\{\{\s*\.ChangeTransferPolicy` ),
762+ "unescaped Go templates should not exist in default values" )
763+ })
764+
765+ It ("should escape multiple template expressions on the same line" , func () {
766+ crdResource := & unstructured.Unstructured {}
767+ crdResource .SetAPIVersion ("apiextensions.k8s.io/v1" )
768+ crdResource .SetKind ("CustomResourceDefinition" )
769+ crdResource .SetName ("policies.example.com" )
770+
771+ content := `apiVersion: apiextensions.k8s.io/v1
772+ kind: CustomResourceDefinition
773+ spec:
774+ versions:
775+ - schema:
776+ openAPIV3Schema:
777+ properties:
778+ spec:
779+ properties:
780+ message:
781+ default: "From {{ .Source.Branch }} to {{ .Target.Branch }}"`
782+
783+ result := templater .ApplyHelmSubstitutions (content , crdResource )
784+
785+ // Both templates should be escaped (applies to all resources)
786+ Expect (result ).To (ContainSubstring (`{{ "{{ .Source.Branch }}" }}` ))
787+ Expect (result ).To (ContainSubstring (`{{ "{{ .Target.Branch }}" }}` ))
788+ })
789+
790+ It ("should escape templates with special characters" , func () {
791+ crdResource := & unstructured.Unstructured {}
792+ crdResource .SetAPIVersion ("apiextensions.k8s.io/v1" )
793+ crdResource .SetKind ("CustomResourceDefinition" )
794+ crdResource .SetName ("configs.example.com" )
795+
796+ content := `apiVersion: apiextensions.k8s.io/v1
797+ kind: CustomResourceDefinition
798+ spec:
799+ versions:
800+ - schema:
801+ openAPIV3Schema:
802+ properties:
803+ spec:
804+ properties:
805+ value:
806+ default: "Value: {{ .Config.Key-With-Dashes }}"`
807+
808+ result := templater .ApplyHelmSubstitutions (content , crdResource )
809+
810+ Expect (result ).To (ContainSubstring (`{{ "{{ .Config.Key-With-Dashes }}" }}` ))
811+ })
812+
813+ It ("should handle template syntax with quotes correctly" , func () {
814+ crdResource := & unstructured.Unstructured {}
815+ crdResource .SetAPIVersion ("apiextensions.k8s.io/v1" )
816+ crdResource .SetKind ("CustomResourceDefinition" )
817+ crdResource .SetName ("messages.example.com" )
818+
819+ content := `apiVersion: apiextensions.k8s.io/v1
820+ kind: CustomResourceDefinition
821+ spec:
822+ versions:
823+ - schema:
824+ openAPIV3Schema:
825+ properties:
826+ spec:
827+ properties:
828+ template:
829+ default: '{{ .Config.Message "default" }}'`
830+
831+ result := templater .ApplyHelmSubstitutions (content , crdResource )
832+
833+ // Quotes inside templates should be escaped
834+ Expect (result ).To (ContainSubstring (`{{ "{{ .Config.Message \"default\" }}" }}` ))
835+ })
836+
837+ It ("should escape templates in ConfigMaps and other non-CRD resources" , func () {
838+ configMapResource := & unstructured.Unstructured {}
839+ configMapResource .SetAPIVersion ("v1" )
840+ configMapResource .SetKind ("ConfigMap" )
841+ configMapResource .SetName ("template-config" )
842+ configMapResource .SetNamespace ("test-project-system" )
843+
844+ // ANY resource can have Go template syntax that needs escaping
845+ // Examples: ConfigMaps with notification templates, Secrets with webhook URLs,
846+ // Deployment annotations with CI/CD metadata, etc.
847+ content := `apiVersion: v1
848+ kind: ConfigMap
849+ metadata:
850+ name: template-config
851+ namespace: test-project-system
852+ labels:
853+ app.kubernetes.io/name: test-project
854+ data:
855+ notification: "Deployed from {{ .Source.Branch }} to {{ .Target.Branch }}"`
856+
857+ result := templater .ApplyHelmSubstitutions (content , configMapResource )
858+
859+ // Existing templates should be escaped (applies to ALL resources, not just CRDs)
860+ Expect (result ).To (ContainSubstring (`{{ "{{ .Source.Branch }}" }}` ))
861+ Expect (result ).To (ContainSubstring (`{{ "{{ .Target.Branch }}" }}` ))
862+
863+ // Helm templates should still be added normally
864+ Expect (result ).To (ContainSubstring ("namespace: {{ .Release.Namespace }}" ))
865+ Expect (result ).To (ContainSubstring (`app.kubernetes.io/name: {{ include "test-project.name" . }}` ))
866+ })
867+
868+ It ("should handle content without any templates" , func () {
869+ configMapResource := & unstructured.Unstructured {}
870+ configMapResource .SetAPIVersion ("v1" )
871+ configMapResource .SetKind ("ConfigMap" )
872+ configMapResource .SetName ("no-template" )
873+
874+ content := `apiVersion: v1
875+ kind: ConfigMap
876+ data:
877+ message: "No templates here"`
878+
879+ result := templater .ApplyHelmSubstitutions (content , configMapResource )
880+
881+ // Should not add any escaping
882+ Expect (result ).To (ContainSubstring (`message: "No templates here"` ))
883+ Expect (result ).NotTo (ContainSubstring (`{{ "{{` ))
884+ })
885+ })
886+
717887 Context ("edge cases" , func () {
718888 It ("should handle empty content" , func () {
719889 testResource := & unstructured.Unstructured {}
0 commit comments