add patch.TOML

This commit is contained in:
Benjamin Elder
2019-11-09 09:29:10 -08:00
parent 895ab239b5
commit c13ad350e9
4 changed files with 303 additions and 0 deletions

2
go.mod
View File

@@ -3,9 +3,11 @@ module sigs.k8s.io/kind
go 1.13
require (
github.com/BurntSushi/toml v0.3.1
github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/mattn/go-isatty v0.0.10
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5

3
go.sum
View File

@@ -1,3 +1,4 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -11,6 +12,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -68,6 +70,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@@ -0,0 +1,100 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package patch
import (
"bytes"
"encoding/json"
burntoml "github.com/BurntSushi/toml"
jsonpatch "github.com/evanphx/json-patch"
toml "github.com/pelletier/go-toml"
yaml "gopkg.in/yaml.v3"
"sigs.k8s.io/kind/pkg/errors"
)
// TOML patches toPatch with the patches (should be TOML merge patches) and patches6902 (should be JSON 6902 patches)
func TOML(toPatch string, patches []string, patches6902 []string) (string, error) {
// convert to JSON for patching
j, err := tomlToJSON([]byte(toPatch))
if err != nil {
return "", err
}
// apply merge patches
for _, patch := range patches {
pj, err := tomlToJSON([]byte(patch))
if err != nil {
return "", err
}
patched, err := jsonpatch.MergePatch(j, pj)
if err != nil {
return "", errors.WithStack(err)
}
j = patched
}
// apply JSON 6902 patches
for _, patch6902 := range patches6902 {
patch, err := jsonpatch.DecodePatch([]byte(patch6902))
if err != nil {
return "", errors.WithStack(err)
}
patched, err := patch.Apply(j)
if err != nil {
return "", errors.WithStack(err)
}
j = patched
}
// convert result back to TOML
return jsonToTOMLString(j)
}
// tomlToJSON converts arbitrary TOML to JSON
func tomlToJSON(t []byte) ([]byte, error) {
// we use github.com.pelletier/go-toml here to unmarshal arbitrary TOML to JSON
tree, err := toml.LoadBytes(t)
if err != nil {
return nil, errors.WithStack(err)
}
b, err := json.Marshal(tree.ToMap())
if err != nil {
return nil, errors.WithStack(err)
}
return b, nil
}
// jsonToTOMLString converts arbitrary JSON to TOML
func jsonToTOMLString(j []byte) (string, error) {
var unstruct interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshalling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
if err := yaml.Unmarshal(j, &unstruct); err != nil {
return "", errors.WithStack(err)
}
// we use github.com/BurntSushi/toml here because github.com.pelletier/go-toml
// can only marshal structs AND BurntSushi/toml is what contained uses
// and has more canonically formatted output (we initially plan to use
// this package for patching containerd config)
var buff bytes.Buffer
if err := burntoml.NewEncoder(&buff).Encode(unstruct); err != nil {
return "", errors.WithStack(err)
}
return buff.String(), nil
}

View File

@@ -0,0 +1,198 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package patch
import (
"testing"
"sigs.k8s.io/kind/pkg/internal/util/assert"
)
func TestTOML(t *testing.T) {
t.Parallel()
type testCase struct {
Name string
ToPatch string
Patches []string
PatchesJSON6902 []string
ExpectError bool
ExpectOutput string
}
cases := []testCase{
{
Name: "invalid TOML",
ToPatch: `🗿`,
ExpectError: true,
ExpectOutput: "",
},
{
Name: "no patches",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
ExpectError: false,
ExpectOutput: `disabled_plugins = ["restart"]
[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.linux]
shim_debug = true
`,
},
{
Name: "invalid patch TOML",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
Patches: []string{"🏰"},
ExpectError: true,
},
{
Name: "invalid 6902 patch JSON",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
PatchesJSON6902: []string{"🏰"},
ExpectError: true,
},
{
Name: "trivial patch",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
Patches: []string{`disabled_plugins=[]`},
ExpectError: false,
ExpectOutput: `[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.linux]
shim_debug = true
`,
},
{
Name: "trivial 6902 patch",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`},
ExpectError: false,
ExpectOutput: `[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.linux]
shim_debug = true
`,
},
{
Name: "trivial patch and trivial 6902 patch",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
Patches: []string{`disabled_plugins=["foo"]`},
PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`},
ExpectError: false,
ExpectOutput: `[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.linux]
shim_debug = true
`,
},
{
Name: "invalid path 6902 patch",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
PatchesJSON6902: []string{`[{"op": "remove", "path": "/fooooooo"}]`},
ExpectError: true,
ExpectOutput: `[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.linux]
shim_debug = true
`,
},
{
Name: "patch registry",
ToPatch: `disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"`,
Patches: []string{`[plugins.cri.registry.mirrors]
[plugins.cri.registry.mirrors."registry:5000"]
endpoint = ["http://registry:5000"]`},
ExpectError: false,
ExpectOutput: `disabled_plugins = ["restart"]
[plugins]
[plugins.cri]
[plugins.cri.containerd]
[plugins.cri.containerd.runtimes]
[plugins.cri.containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins.cri.registry]
[plugins.cri.registry.mirrors]
[plugins.cri.registry.mirrors."registry:5000"]
endpoint = ["http://registry:5000"]
[plugins.linux]
shim_debug = true
`,
},
}
for _, tc := range cases {
tc := tc // capture test case
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
out, err := TOML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902)
assert.ExpectError(t, tc.ExpectError, err)
if err == nil {
assert.StringEqual(t, tc.ExpectOutput, out)
}
})
}
}