@@ -145,29 +145,147 @@ func LoadHelmChart(path string, w io.Writer, extractOnlyCrds bool, kubeVersion s
145
145
if err != nil {
146
146
return ErrLoadHelmChart (err , path )
147
147
}
148
- if extractOnlyCrds {
149
- crds := chart .CRDObjects ()
150
- size := len (crds )
151
- for index , crd := range crds {
152
- _ , err := w .Write (crd .File .Data )
153
- if err != nil {
154
- errs = append (errs , err )
155
- continue
156
- }
157
- if index == size - 1 {
158
- break
159
- }
160
- _ , _ = w .Write ([]byte ("\n ---\n " ))
161
- }
162
- } else {
148
+
149
+ if ! extractOnlyCrds {
163
150
manifests , err := DryRunHelmChart (chart , kubeVersion )
164
151
if err != nil {
165
152
return ErrLoadHelmChart (err , path )
166
153
}
167
154
_ , err = w .Write (manifests )
155
+ return err
156
+ }
157
+
158
+ // Look for all the yaml file in the helm dir that is a CRD
159
+ err = filepath .WalkDir (path , func (filePath string , d fs.DirEntry , err error ) error {
168
160
if err != nil {
169
161
return ErrLoadHelmChart (err , path )
170
162
}
163
+ if ! d .IsDir () && (strings .HasSuffix (filePath , ".yaml" ) || strings .HasSuffix (filePath , ".yml" )) {
164
+ data , err := os .ReadFile (filePath )
165
+ if err != nil {
166
+ return err
167
+ }
168
+
169
+ if isCRDFile (data ) {
170
+ data = RemoveHelmPlaceholders (data )
171
+ if err := writeToWriter (w , data ); err != nil {
172
+ errs = append (errs , err )
173
+ }
174
+ }
175
+ }
176
+ return nil
177
+ })
178
+
179
+ if err != nil {
180
+ errs = append (errs , err )
171
181
}
182
+
172
183
return utils .CombineErrors (errs , "\n " )
173
184
}
185
+
186
+ func writeToWriter (w io.Writer , data []byte ) error {
187
+ trimmedData := bytes .TrimSpace (data )
188
+
189
+ if len (trimmedData ) == 0 {
190
+ return nil
191
+ }
192
+
193
+ // Check if the document already starts with separators
194
+ startsWithSeparator := bytes .HasPrefix (trimmedData , []byte ("---" ))
195
+
196
+ // If it doesn't start with ---, add one
197
+ if ! startsWithSeparator {
198
+ if _ , err := w .Write ([]byte ("---\n " )); err != nil {
199
+ return err
200
+ }
201
+ }
202
+
203
+ if _ , err := w .Write (trimmedData ); err != nil {
204
+ return err
205
+ }
206
+
207
+ _ , err := w .Write ([]byte ("\n " ))
208
+ return err
209
+ }
210
+
211
+ // checks if the content is a CRD
212
+ // NOTE: kubernetes.IsCRD(manifest string) already exists however using that leads to cyclic dependency
213
+ func isCRDFile (content []byte ) bool {
214
+ str := string (content )
215
+ return strings .Contains (str , "kind: CustomResourceDefinition" )
216
+ }
217
+
218
+ // RemoveHelmPlaceholders - replaces helm templates placeholder with YAML compatible empty value
219
+ // since these templates cause YAML parsing error
220
+ // NOTE: this is a quick fix
221
+ func RemoveHelmPlaceholders (data []byte ) []byte {
222
+ content := string (data )
223
+
224
+ // Regular expressions to match different Helm template patterns
225
+ // Match multiline template blocks that start with {{- and end with }}
226
+ multilineRegex := regexp .MustCompile (`(?s){{-?\s*.*?\s*}}` )
227
+
228
+ // Match single line template expressions
229
+ singleLineRegex := regexp .MustCompile (`{{-?\s*[^}]*}}` )
230
+
231
+ // Process the content line by line to maintain YAML structure
232
+ lines := strings .Split (content , "\n " )
233
+ var processedLines []string
234
+
235
+ for _ , line := range lines {
236
+ trimmedLine := strings .TrimSpace (line )
237
+ if trimmedLine == "" {
238
+ processedLines = append (processedLines , line )
239
+ continue
240
+ }
241
+
242
+ // Handle multiline template blocks first
243
+ if multilineRegex .MatchString (line ) {
244
+ // If line starts with indentation + list marker
245
+ if listMatch := regexp .MustCompile (`^(\s*)- ` ).FindStringSubmatch (line ); listMatch != nil {
246
+ // Convert list item to empty map to maintain structure
247
+ processedLines = append (processedLines , listMatch [1 ]+ "- {}" )
248
+ continue
249
+ }
250
+
251
+ // If it's a value assignment with multiline template
252
+ if valueMatch := regexp .MustCompile (`^(\s*)(\w+):\s*{{` ).FindStringSubmatch (line ); valueMatch != nil {
253
+ // Preserve the key with empty map value
254
+ processedLines = append (processedLines , valueMatch [1 ]+ valueMatch [2 ]+ ": {}" )
255
+ continue
256
+ }
257
+
258
+ // For other multiline templates, replace with empty line
259
+ processedLines = append (processedLines , "" )
260
+ continue
261
+ }
262
+
263
+ // Handle single line template expressions
264
+ if singleLineRegex .MatchString (line ) {
265
+ // If line contains a key-value pair
266
+ if keyMatch := regexp .MustCompile (`^(\s*)(\w+):\s*{{` ).FindStringSubmatch (line ); keyMatch != nil {
267
+ // Preserve the key with empty string value
268
+ processedLines = append (processedLines , keyMatch [1 ]+ keyMatch [2 ]+ ": " )
269
+ continue
270
+ }
271
+
272
+ // If line is a list item
273
+ if listMatch := regexp .MustCompile (`^(\s*)- ` ).FindStringSubmatch (line ); listMatch != nil {
274
+ // Convert to empty map to maintain list structure
275
+ processedLines = append (processedLines , listMatch [1 ]+ "- {}" )
276
+ continue
277
+ }
278
+
279
+ // For standalone template expressions, remove them (includes, control statements)
280
+ line = singleLineRegex .ReplaceAllString (line , "" )
281
+ if strings .TrimSpace (line ) != "" {
282
+ processedLines = append (processedLines , line )
283
+ }
284
+ continue
285
+ }
286
+
287
+ processedLines = append (processedLines , line )
288
+ }
289
+
290
+ return []byte (strings .Join (processedLines , "\n " ))
291
+ }
0 commit comments