@@ -6,11 +6,14 @@ package limatmpl
66import (
77 "bytes"
88 "context"
9+ "encoding/base64"
910 "fmt"
1011 "os"
1112 "path/filepath"
1213 "slices"
14+ "strings"
1315 "sync"
16+ "unicode"
1417
1518 "github.com/coreos/go-semver/semver"
1619 "github.com/lima-vm/lima/pkg/limayaml"
@@ -254,7 +257,7 @@ const mergeDocuments = `
254257| $a | (select(.mountTypesUnsupported) | .mountTypesUnsupported) |= unique
255258
256259# Remove the custom tags again so they do not clutter up the YAML output.
257- | $a | .. tag = ""
260+ | $a | .. | select(tag == "!!tag") tag = ""
258261`
259262
260263// listFields returns dst and src fields like "list[idx].field".
@@ -552,11 +555,72 @@ func (tmpl *Template) combineNetworks() {
552555 }
553556}
554557
558+ // yamlfmt will fail with a buffer overflow while trying to retain line breaks if the line
559+ // is longer than 64K. We will encode all text files that have a line that comes close.
560+ // maxLineLength is a constant; it is only a variable for the benefit of the unit tests.
561+ var maxLineLength = 65000
562+
563+ // encodeScriptReason returns the reason why a script needs to be base64 encoded or the empty string if it doesn't.
564+ func encodeScriptReason (script string ) string {
565+ start := 0
566+ line := 1
567+ for i , r := range script {
568+ if ! (unicode .IsPrint (r ) || r == '\n' || r == '\r' || r == '\t' ) {
569+ return fmt .Sprintf ("unprintable character %q at offset %d" , r , i )
570+ }
571+ // maxLineLength includes final newline
572+ if i - start >= maxLineLength {
573+ return fmt .Sprintf ("line %d (offset %d) is longer than %d characters" , line , start , maxLineLength )
574+ }
575+ if r == '\n' {
576+ line ++
577+ start = i + 1
578+ }
579+ }
580+ return ""
581+ }
582+
583+ // Break base64 strings into shorter chunks. Technically we could use maxLineLength here,
584+ // but shorter lines look better.
585+ const base64ChunkLength = 76
586+
587+ // binaryString returns a base64 encoded version of the binary string, broken into chunks
588+ // of at most base64ChunkLength characters per line.
589+ func binaryString (s string ) string {
590+ encoded := base64 .StdEncoding .EncodeToString ([]byte (s ))
591+ if len (encoded ) <= base64ChunkLength {
592+ return encoded
593+ }
594+
595+ // Estimate capacity: encoded length + number of newlines
596+ lineCount := (len (encoded ) + base64ChunkLength - 1 ) / base64ChunkLength
597+ builder := strings.Builder {}
598+ builder .Grow (len (encoded ) + lineCount )
599+
600+ for i := 0 ; i < len (encoded ); i += base64ChunkLength {
601+ end := i + base64ChunkLength
602+ if end > len (encoded ) {
603+ end = len (encoded )
604+ }
605+ builder .WriteString (encoded [i :end ])
606+ builder .WriteByte ('\n' )
607+ }
608+
609+ return builder .String ()
610+ }
611+
555612// updateScript replaces a "file" property with the actual script and then renames the field to newName ("script" or "content").
556- func (tmpl * Template ) updateScript (field string , idx int , newName , script string ) {
613+ func (tmpl * Template ) updateScript (field string , idx int , newName , script , file string ) {
614+ tag := ""
615+ if reason := encodeScriptReason (script ); reason != "" {
616+ logrus .Infof ("File %q is being base64 encoded: %s" , file , reason )
617+ script = binaryString (script )
618+ tag = "!!binary"
619+ }
557620 entry := fmt .Sprintf ("$a.%s[%d].file" , field , idx )
558- // Assign script to the "file" field and then rename it to "script".
559- tmpl .expr .WriteString (fmt .Sprintf ("| (%s) = %q | (%s | key) = %q\n " , entry , script , entry , newName ))
621+ // Assign script to the "file" field and then rename it to "script" or "content".
622+ tmpl .expr .WriteString (fmt .Sprintf ("| (%s) = %q | (%s) tag = %q | (%s | key) = %q\n " ,
623+ entry , script , entry , tag , entry , newName ))
560624}
561625
562626// embedAllScripts replaces all "provision" and "probes" file references with the actual script.
@@ -579,7 +643,7 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
579643 if err != nil {
580644 return err
581645 }
582- tmpl .updateScript ("probes" , i , "script" , string (scriptTmpl .Bytes ))
646+ tmpl .updateScript ("probes" , i , "script" , string (scriptTmpl .Bytes ), p . File . URL )
583647 }
584648 }
585649 for i , p := range tmpl .Config .Provision {
@@ -605,7 +669,7 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error
605669 if err != nil {
606670 return err
607671 }
608- tmpl .updateScript ("provision" , i , newName , string (scriptTmpl .Bytes ))
672+ tmpl .updateScript ("provision" , i , newName , string (scriptTmpl .Bytes ), p . File . URL )
609673 }
610674 }
611675 return tmpl .evalExpr ()
0 commit comments