Skip to content

Commit

Permalink
fix: allow META encoded values to be compressed
Browse files Browse the repository at this point in the history
Fixes siderolabs#8186

This is planned to be backported to Talos 1.6.3.

This allows to pass large META values (YAML for platform network
configuration) which might otherwise exceed the limit for kernel
command line params.

Signed-off-by: Andrey Smirnov <[email protected]>
(cherry picked from commit e0dfbb8)
  • Loading branch information
smira committed Jan 24, 2024
1 parent 56e87f5 commit 815fef8
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 15 deletions.
2 changes: 1 addition & 1 deletion cmd/installer/pkg/install/meta_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (s *MetaValues) GetSlice() []string {

// Encode returns the encoded values.
func (s *MetaValues) Encode() string {
return s.values.Encode()
return s.values.Encode(false)
}

// Decode the values from the given string.
Expand Down
6 changes: 5 additions & 1 deletion pkg/imager/imager.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/siderolabs/talos/internal/pkg/secureboot/uki"
"github.com/siderolabs/talos/pkg/imager/extensions"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/imager/quirks"
"github.com/siderolabs/talos/pkg/imager/utils"
"github.com/siderolabs/talos/pkg/machinery/config/merge"
"github.com/siderolabs/talos/pkg/machinery/constants"
Expand Down Expand Up @@ -276,7 +277,10 @@ func (i *Imager) buildCmdline() error {
// meta values can be written only to the "image" output
if len(i.prof.Customization.MetaContents) > 0 && i.prof.Output.Kind != profile.OutKindImage {
// pass META values as kernel talos.environment args which will be passed via the environment to the installer
cmdline.Append(constants.KernelParamEnvironment, constants.MetaValuesEnvVar+"="+i.prof.Customization.MetaContents.Encode())
cmdline.Append(
constants.KernelParamEnvironment,
constants.MetaValuesEnvVar+"="+i.prof.Customization.MetaContents.Encode(quirks.New(i.prof.Version).SupportsCompressedEncodedMETA()),
)
}

// apply customization
Expand Down
12 changes: 12 additions & 0 deletions pkg/imager/quirks/quirks.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ func (q Quirks) SupportsResetGRUBOption() bool {

return q.v.GTE(minVersionResetOption)
}

var minVersionCompressedMETA = semver.MustParse("1.6.3")

// SupportsCompressedEncodedMETA returns true if the Talos version supports compressed and encoded META as an environment variable.
func (q Quirks) SupportsCompressedEncodedMETA() bool {
// if the version doesn't parse, we assume it's latest Talos
if q.v == nil {
return true
}

return q.v.GTE(minVersionCompressedMETA)
}
28 changes: 28 additions & 0 deletions pkg/imager/quirks/quirks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,31 @@ func TestSupportsResetOption(t *testing.T) {
})
}
}

func TestSupportsCompressedEncodedMETA(t *testing.T) {
for _, test := range []struct {
version string

expected bool
}{
{
version: "1.6.3",
expected: true,
},
{
version: "1.7.0",
expected: true,
},
{
expected: true,
},
{
version: "1.6.2",
expected: false,
},
} {
t.Run(test.version, func(t *testing.T) {
assert.Equal(t, test.expected, quirks.New(test.version).SupportsCompressedEncodedMETA())
})
}
}
56 changes: 54 additions & 2 deletions pkg/machinery/meta/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
package meta

import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"strconv"
"strings"

Expand Down Expand Up @@ -49,8 +52,29 @@ type Values []Value
//
// Each Value is encoded a k=v, split by ';' character.
// The result is base64 encoded.
func (v Values) Encode() string {
return base64.StdEncoding.EncodeToString([]byte(strings.Join(xslices.Map(v, Value.String), ";")))
func (v Values) Encode(allowGzip bool) string {
raw := []byte(strings.Join(xslices.Map(v, Value.String), ";"))

if allowGzip && len(raw) > 256 {
var buf bytes.Buffer

gzW, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
panic(err)
}

if _, err := gzW.Write(raw); err != nil {
panic(err)
}

if err := gzW.Close(); err != nil {
panic(err)
}

raw = buf.Bytes()
}

return base64.StdEncoding.EncodeToString(raw)
}

// DecodeValues parses a string representation of Values for the environment variable.
Expand All @@ -66,6 +90,25 @@ func DecodeValues(s string) (Values, error) {
return nil, nil
}

// do un-gzip if needed
if hasGzipMagic(b) {
gzR, err := gzip.NewReader(bytes.NewReader(b))
if err != nil {
return nil, err
}

defer gzR.Close() //nolint:errcheck

b, err = io.ReadAll(gzR)
if err != nil {
return nil, err
}

if err := gzR.Close(); err != nil {
return nil, err
}
}

parts := strings.Split(string(b), ";")

result := make(Values, 0, len(parts))
Expand All @@ -82,3 +125,12 @@ func DecodeValues(s string) (Values, error) {

return result, nil
}

func hasGzipMagic(b []byte) bool {
if len(b) < 10 {
return false
}

// See https://en.wikipedia.org/wiki/Gzip#File_format.
return b[0] == 0x1f && b[1] == 0x8b
}
83 changes: 72 additions & 11 deletions pkg/machinery/meta/meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package meta_test

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -35,15 +37,74 @@ func TestValue(t *testing.T) {
func TestEncodeDecodeValues(t *testing.T) {
t.Parallel()

values := make(meta.Values, 2)

require.NoError(t, values[0].Parse("10=foo"))
require.NoError(t, values[1].Parse("0xb=bar"))

encoded := values.Encode()

decoded, err := meta.DecodeValues(encoded)
require.NoError(t, err)

assert.Equal(t, values, decoded)
for _, allowGzip := range []bool{false, true} {
allowGzip := allowGzip

t.Run(fmt.Sprintf("allowGzip=%v", allowGzip), func(t *testing.T) {
t.Parallel()

for _, test := range []struct {
name string

values []string

expectedEncodedSize int
expectedGzippedSize int
}{
{
name: "empty",
},
{
name: "simple",
values: []string{
"10=foo",
"0xb=bar",
},

expectedEncodedSize: 20,
expectedGzippedSize: 20,
},
{
name: "huge",
values: []string{
"10=" + strings.Repeat("foobar", 256),
"0xb=" + strings.Repeat("baz", 256),
},

expectedEncodedSize: 3084,
expectedGzippedSize: 80,
},
} {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

values := make(meta.Values, len(test.values))

for i, v := range test.values {
require.NoError(t, values[i].Parse(v))
}

if len(values) == 0 {
values = nil
}

encoded := values.Encode(allowGzip)

switch {
case test.expectedEncodedSize > 0 && !allowGzip:
assert.Equal(t, test.expectedEncodedSize, len(encoded))
case test.expectedGzippedSize > 0 && allowGzip:
assert.Equal(t, test.expectedGzippedSize, len(encoded))
}

decoded, err := meta.DecodeValues(encoded)
require.NoError(t, err)

assert.Equal(t, values, decoded)
})
}
})
}
}

0 comments on commit 815fef8

Please sign in to comment.