From c22588bf0d09fafb2ae484d6de6c404774cae8d8 Mon Sep 17 00:00:00 2001 From: Jeremi Piotrowski Date: Fri, 5 Nov 2021 10:22:00 +0000 Subject: [PATCH 1/2] config/types/files: encode file contents as base64 when the result is smaller Ignition encodes file contents in the dataurl format. Dataurl supports either url-escaped or base64 encoded content. CLCT always url-escapes content, but there are cases where base64 results in smaller text. One example is compressed content, which ignition should support but ours doesn't due to a check which can be removed (see [1]). So this is step 1 in enabling CLCT to transparently compress data. Data is encoded as both url-escaped and base64 and the result lengths are tested to see which one to use. It is possible to get CLCT to ingest gzipped data by manually doing 'gzip+base64', using some yaml magic and abusing CLCT parsing (slightly): storage: files: - path: /file/path mode: 0644 contents: remote: compression: gzip inline: !!binary | H4sIAAAAAAAAA8tIzcnJ5wIAIDA6NgYAAAA= This results in spec compliant ignition configuration, that is not yet handled by our version of the ignition program. This is based on similar work done in butane[2]. [1]: https://github.com/coreos/ignition/commit/f770885e2f2ab2905354bb90546e3ad1a9cfde4d [2]: https://github.com/coreos/butane/blob/07daeed0f1113acffbc21b47e2ecc997d44e7943/base/util/url.go#L31-L37 --- config/types/files.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/config/types/files.go b/config/types/files.go index a429768..b3bf79d 100644 --- a/config/types/files.go +++ b/config/types/files.go @@ -15,6 +15,7 @@ package types import ( + "encoding/base64" "errors" "flag" "fmt" @@ -129,6 +130,19 @@ func (fc FileContents) Validate() report.Report { return report.Report{} } +func makeDataUrl(contents []byte) string { + opaque := "," + dataurl.Escape(contents) + b64 := ";base64," + base64.StdEncoding.EncodeToString(contents) + if len(b64) < len(opaque) { + opaque = b64 + } + uri := (&url.URL{ + Scheme: "data", + Opaque: opaque, + }).String() + return uri +} + func init() { register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) { r := report.Report{} @@ -167,10 +181,7 @@ func init() { if file.Contents.Inline != "" { newFile.Contents = ignTypes.FileContents{ - Source: (&url.URL{ - Scheme: "data", - Opaque: "," + dataurl.EscapeString(file.Contents.Inline), - }).String(), + Source: makeDataUrl([]byte(file.Contents.Inline)), } } @@ -203,10 +214,7 @@ func init() { // Include the contents of the local file as if it were provided inline. newFile.Contents = ignTypes.FileContents{ - Source: (&url.URL{ - Scheme: "data", - Opaque: "," + dataurl.Escape(contents), - }).String(), + Source: makeDataUrl(contents), } } From 324132fb5412a94ee93c66f7f945932af544009b Mon Sep 17 00:00:00 2001 From: Jeremi Piotrowski Date: Tue, 18 Jan 2022 18:22:31 +0100 Subject: [PATCH 2/2] config/types/files: add unit test for makeDataUrl function Signed-off-by: Jeremi Piotrowski --- config/types/files_test.go | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 config/types/files_test.go diff --git a/config/types/files_test.go b/config/types/files_test.go new file mode 100644 index 0000000..c128e0e --- /dev/null +++ b/config/types/files_test.go @@ -0,0 +1,43 @@ +package types + +import ( + "github.com/vincent-petithory/dataurl" + "strings" + "testing" +) + +func TestDataURL(t *testing.T) { + testString1 := "hello world" + dataUrl1 := makeDataUrl([]byte(testString1)) + decodedUrl1, err := dataurl.DecodeString(dataUrl1) + if err != nil { + t.Fatalf("DecodeString #1 failed: %v", err) + } + resultStr1 := string(decodedUrl1.Data) + if strings.HasPrefix(dataUrl1, "data:;base64,") { + t.Errorf("base64 encoding used instead of url encoding: %v", dataUrl1) + } + if resultStr1 != testString1 { + t.Errorf("wanted %v, got %v", testString1, resultStr1) + } + + testString2 := ` +#!/bin/bash +msg="hello" +f() { + ( echo "${msg}" ) & +} +f` + dataUrl2 := makeDataUrl([]byte(testString2)) + if !strings.HasPrefix(dataUrl2, "data:;base64,") { + t.Errorf("base64 encoding missing: %v", dataUrl2) + } + decodedUrl2, err := dataurl.DecodeString(dataUrl2) + if err != nil { + t.Fatalf("DecodeString #2 failed: %v", err) + } + resultStr2 := string(decodedUrl2.Data) + if resultStr2 != testString2 { + t.Errorf("wanted %v, got %v", testString2, resultStr2) + } +}