Skip to content

Commit b85d51c

Browse files
authored
Merge pull request #654 from souvikinator/souvik/fix/linkerd-model-generation
Add support for Helm CRD template sanitization to fix YAML parsing for Linkerd
2 parents 8fb512c + 8aaf8ff commit b85d51c

File tree

3 files changed

+146
-32
lines changed

3 files changed

+146
-32
lines changed

generators/github/git_repo.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ func (gr GitRepo) GetContent() (models.Package, error) {
3939
dirPath := filepath.Join(os.TempDir(), owner, repo, branch)
4040
_ = os.MkdirAll(dirPath, 0755)
4141
filePath := filepath.Join(dirPath, utils.GetRandomAlphabetsOfDigit(5))
42-
fd, err := os.Create(filePath) // perform cleanup
42+
fd, err := os.Create(filePath)
4343
if err != nil {
44+
os.RemoveAll(dirPath)
4445
return nil, utils.ErrCreateFile(err, filePath)
4546
}
4647
br := bufio.NewWriter(fd)
4748

4849
defer func() {
49-
_ = br.Flush()
50+
br.Flush()
51+
fd.Close()
5052
}()
5153
gw := gitWalker.
5254
Owner(owner).

utils/helm/helm.go

Lines changed: 133 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -145,29 +145,147 @@ func LoadHelmChart(path string, w io.Writer, extractOnlyCrds bool, kubeVersion s
145145
if err != nil {
146146
return ErrLoadHelmChart(err, path)
147147
}
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 {
163150
manifests, err := DryRunHelmChart(chart, kubeVersion)
164151
if err != nil {
165152
return ErrLoadHelmChart(err, path)
166153
}
167154
_, 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 {
168160
if err != nil {
169161
return ErrLoadHelmChart(err, path)
170162
}
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)
171181
}
182+
172183
return utils.CombineErrors(errs, "\n")
173184
}
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+
}

utils/utils.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -841,33 +841,27 @@ func ParseKubeStatusErr(err *kubeerror.StatusError) (shortDescription, longDescr
841841

842842
status := err.Status()
843843

844-
// Create base description including status code
845-
baseDesc := fmt.Sprintf("[Status Code: %d] %s", status.Code, status.Reason)
846-
if status.Details != nil {
847-
baseDesc = fmt.Sprintf("%s: %s '%s'", baseDesc, status.Details.Kind, status.Details.Name)
848-
}
849-
shortDescription = append(shortDescription, baseDesc)
850-
851-
// Add the main error message
852-
longDescription = append(longDescription, status.Message)
844+
// Add the high-level error message with status code to longDescription
845+
longDescription = append(longDescription, fmt.Sprintf("[Status Code: %d] %s", status.Code, status.Message))
853846

854-
// Add reason-based guidance
855847
pc, rem := handleStatusReason(status.Reason)
856848
probableCause = append(probableCause, pc)
857849
remedy = append(remedy, rem)
858850

859-
// Process detailed causes if available
851+
// Add specific field validation errors
860852
if status.Details != nil && len(status.Details.Causes) > 0 {
861853
for _, cause := range status.Details.Causes {
862-
// Add detailed cause description
863-
longDescription = append(longDescription,
864-
fmt.Sprintf("Field '%s': %s", cause.Field, cause.Message))
854+
longDescription = append(longDescription, fmt.Sprintf("Field '%s': %s", cause.Field, cause.Message))
865855

866-
// Get cause-specific messages
867856
pc, rem := handleStatusCause(cause, status.Details.Kind)
868857
probableCause = append(probableCause, pc)
869858
remedy = append(remedy, rem)
870859
}
860+
} else {
861+
// If no specific causes are provided, add the general reason-based guidance
862+
pc, rem := handleStatusReason(status.Reason)
863+
probableCause = append(probableCause, pc)
864+
remedy = append(remedy, rem)
871865
}
872866

873867
return

0 commit comments

Comments
 (0)