mirror of https://github.com/k3s-io/k3s
Use dry-run patch to get the merged version of the object
parent
0787715978
commit
2d3f7795c8
|
@ -60,8 +60,6 @@ go_library(
|
||||||
"//pkg/api/legacyscheme:go_default_library",
|
"//pkg/api/legacyscheme:go_default_library",
|
||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//pkg/kubectl:go_default_library",
|
"//pkg/kubectl:go_default_library",
|
||||||
"//pkg/kubectl/apply/parse:go_default_library",
|
|
||||||
"//pkg/kubectl/apply/strategy:go_default_library",
|
|
||||||
"//pkg/kubectl/cmd/auth:go_default_library",
|
"//pkg/kubectl/cmd/auth:go_default_library",
|
||||||
"//pkg/kubectl/cmd/config:go_default_library",
|
"//pkg/kubectl/cmd/config:go_default_library",
|
||||||
"//pkg/kubectl/cmd/create:go_default_library",
|
"//pkg/kubectl/cmd/create:go_default_library",
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -25,19 +24,20 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
|
"github.com/jonboulle/clockwork"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
|
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply/parse"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/apply/strategy"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||||
"k8s.io/utils/exec"
|
"k8s.io/utils/exec"
|
||||||
)
|
)
|
||||||
|
@ -130,7 +130,7 @@ func (d *DiffProgram) Run(from, to string) error {
|
||||||
type Printer struct{}
|
type Printer struct{}
|
||||||
|
|
||||||
// Print the object inside the writer w.
|
// Print the object inside the writer w.
|
||||||
func (p *Printer) Print(obj map[string]interface{}, w io.Writer) error {
|
func (p *Printer) Print(obj runtime.Object, w io.Writer) error {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -161,10 +161,10 @@ func NewDiffVersion(name string) (*DiffVersion, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DiffVersion) getObject(obj Object) (map[string]interface{}, error) {
|
func (v *DiffVersion) getObject(obj Object) (runtime.Object, error) {
|
||||||
switch v.Name {
|
switch v.Name {
|
||||||
case "LIVE":
|
case "LIVE":
|
||||||
return obj.Live()
|
return obj.Live(), nil
|
||||||
case "MERGED":
|
case "MERGED":
|
||||||
return obj.Merged()
|
return obj.Merged()
|
||||||
}
|
}
|
||||||
|
@ -216,8 +216,8 @@ func (d *Directory) Delete() error {
|
||||||
// Object is an interface that let's you retrieve multiple version of
|
// Object is an interface that let's you retrieve multiple version of
|
||||||
// it.
|
// it.
|
||||||
type Object interface {
|
type Object interface {
|
||||||
Live() (map[string]interface{}, error)
|
Live() runtime.Object
|
||||||
Merged() (map[string]interface{}, error)
|
Merged() (runtime.Object, error)
|
||||||
|
|
||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
@ -225,73 +225,51 @@ type Object interface {
|
||||||
// InfoObject is an implementation of the Object interface. It gets all
|
// InfoObject is an implementation of the Object interface. It gets all
|
||||||
// the information from the Info object.
|
// the information from the Info object.
|
||||||
type InfoObject struct {
|
type InfoObject struct {
|
||||||
Remote *unstructured.Unstructured
|
LocalObj runtime.Object
|
||||||
Info *resource.Info
|
Info *resource.Info
|
||||||
Encoder runtime.Encoder
|
Encoder runtime.Encoder
|
||||||
Parser *parse.Factory
|
OpenAPI openapi.Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Object = &InfoObject{}
|
var _ Object = &InfoObject{}
|
||||||
|
|
||||||
func (obj InfoObject) toMap(data []byte) (map[string]interface{}, error) {
|
// Returns the live version of the object
|
||||||
m := map[string]interface{}{}
|
func (obj InfoObject) Live() runtime.Object {
|
||||||
if len(data) == 0 {
|
return obj.Info.Object
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(data, &m)
|
|
||||||
return m, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj InfoObject) Live() (map[string]interface{}, error) {
|
// Returns the "merged" object, as it would look like if applied or
|
||||||
if obj.Remote == nil {
|
// created.
|
||||||
return nil, nil // Object doesn't exist on cluster.
|
func (obj InfoObject) Merged() (runtime.Object, error) {
|
||||||
}
|
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
|
||||||
return obj.Remote.UnstructuredContent(), nil
|
if obj.Live() == nil {
|
||||||
|
// Dry-run create if the object doesn't exist.
|
||||||
|
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Create(
|
||||||
|
obj.Info.Namespace,
|
||||||
|
true,
|
||||||
|
obj.LocalObj,
|
||||||
|
&metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj InfoObject) Merged() (map[string]interface{}, error) {
|
modified, err := kubectl.GetModifiedConfiguration(obj.LocalObj, false, unstructured.UnstructuredJSONScheme)
|
||||||
data, err := runtime.Encode(obj.Encoder, obj.Info.Object)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
local, err := obj.toMap(data)
|
|
||||||
|
|
||||||
live, err := obj.Live()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
last, err := obj.Last()
|
// This is using the patcher from apply, to keep the same behavior.
|
||||||
if err != nil {
|
// We plan on replacing this with server-side apply when it becomes available.
|
||||||
return nil, err
|
patcher := &patcher{
|
||||||
|
mapping: obj.Info.Mapping,
|
||||||
|
helper: resource.NewHelper(obj.Info.Client, obj.Info.Mapping),
|
||||||
|
overwrite: true,
|
||||||
|
backOff: clockwork.NewRealClock(),
|
||||||
|
serverDryRun: true,
|
||||||
|
openapiSchema: obj.OpenAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
if live == nil || last == nil {
|
_, result, err := patcher.patch(obj.Info.Object, modified, obj.Info.Source, obj.Info.Namespace, obj.Info.Name, nil)
|
||||||
return local, nil // We probably don't have a live version, merged is local.
|
return result, err
|
||||||
}
|
|
||||||
|
|
||||||
elmt, err := obj.Parser.CreateElement(last, local, live)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result, err := elmt.Merge(strategy.Create(strategy.Options{}))
|
|
||||||
return result.MergedResult.(map[string]interface{}), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj InfoObject) Last() (map[string]interface{}, error) {
|
|
||||||
if obj.Remote == nil {
|
|
||||||
return nil, nil // No object is live, return empty
|
|
||||||
}
|
|
||||||
accessor, err := meta.Accessor(obj.Remote)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
annots := accessor.GetAnnotations()
|
|
||||||
if annots == nil {
|
|
||||||
return nil, nil // Not an error, just empty.
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj.toMap([]byte(annots[corev1.LastAppliedConfigAnnotation]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj InfoObject) Name() string {
|
func (obj InfoObject) Name() string {
|
||||||
|
@ -342,59 +320,14 @@ func (d *Differ) TearDown() {
|
||||||
d.To.Dir.Delete() // Ignore error
|
d.To.Dir.Delete() // Ignore error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Downloader struct {
|
|
||||||
mapper meta.RESTMapper
|
|
||||||
dclient dynamic.Interface
|
|
||||||
ns string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDownloader(f cmdutil.Factory) (*Downloader, error) {
|
|
||||||
var err error
|
|
||||||
var d Downloader
|
|
||||||
|
|
||||||
d.mapper, err = f.ToRESTMapper()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d.dclient, err = f.DynamicClient()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d.ns, _, _ = f.ToRawKubeConfigLoader().Namespace()
|
|
||||||
|
|
||||||
return &d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) Download(info *resource.Info) (*unstructured.Unstructured, error) {
|
|
||||||
gvk := info.Object.GetObjectKind().GroupVersionKind()
|
|
||||||
mapping, err := d.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resource dynamic.ResourceInterface
|
|
||||||
switch mapping.Scope.Name() {
|
|
||||||
case meta.RESTScopeNameNamespace:
|
|
||||||
if info.Namespace == "" {
|
|
||||||
info.Namespace = d.ns
|
|
||||||
}
|
|
||||||
resource = d.dclient.Resource(mapping.Resource).Namespace(info.Namespace)
|
|
||||||
case meta.RESTScopeNameRoot:
|
|
||||||
resource = d.dclient.Resource(mapping.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resource.Get(info.Name, metav1.GetOptions{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunDiff uses the factory to parse file arguments, find the version to
|
// RunDiff uses the factory to parse file arguments, find the version to
|
||||||
// diff, and find each Info object for each files, and runs against the
|
// diff, and find each Info object for each files, and runs against the
|
||||||
// differ.
|
// differ.
|
||||||
func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error {
|
func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error {
|
||||||
openapi, err := f.OpenAPISchema()
|
schema, err := f.OpenAPISchema()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parser := &parse.Factory{Resources: openapi}
|
|
||||||
|
|
||||||
differ, err := NewDiffer("LIVE", "MERGED")
|
differ, err := NewDiffer("LIVE", "MERGED")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -413,29 +346,30 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error {
|
||||||
Unstructured().
|
Unstructured().
|
||||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||||
FilenameParam(enforceNamespace, &options.FilenameOptions).
|
FilenameParam(enforceNamespace, &options.FilenameOptions).
|
||||||
Local().
|
|
||||||
Flatten().
|
Flatten().
|
||||||
Do()
|
Do()
|
||||||
if err := r.Err(); err != nil {
|
if err := r.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dl, err := NewDownloader(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Visit(func(info *resource.Info, err error) error {
|
err = r.Visit(func(info *resource.Info, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
remote, _ := dl.Download(info)
|
local := info.Object.DeepCopyObject()
|
||||||
|
if err := info.Get(); err != nil {
|
||||||
|
if !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info.Object = nil
|
||||||
|
}
|
||||||
|
|
||||||
obj := InfoObject{
|
obj := InfoObject{
|
||||||
Remote: remote,
|
LocalObj: local,
|
||||||
Info: info,
|
Info: info,
|
||||||
Parser: parser,
|
Encoder: scheme.DefaultJSONEncoder(),
|
||||||
Encoder: cmdutil.InternalVersionJSONEncoder(),
|
OpenAPI: schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
return differ.Diff(obj, printer)
|
return differ.Diff(obj, printer)
|
||||||
|
@ -444,7 +378,8 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
differ.Run(diff)
|
// Error ignore on purpose. diff(1) for example, returns an error if there is any diff.
|
||||||
|
_ = differ.Run(diff)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
"k8s.io/utils/exec"
|
"k8s.io/utils/exec"
|
||||||
)
|
)
|
||||||
|
@ -41,12 +43,12 @@ func (f *FakeObject) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeObject) Merged() (map[string]interface{}, error) {
|
func (f *FakeObject) Merged() (runtime.Object, error) {
|
||||||
return f.merged, nil
|
return &unstructured.Unstructured{Object: f.merged}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeObject) Live() (map[string]interface{}, error) {
|
func (f *FakeObject) Live() runtime.Object {
|
||||||
return f.live, nil
|
return &unstructured.Unstructured{Object: f.live}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiffProgram(t *testing.T) {
|
func TestDiffProgram(t *testing.T) {
|
||||||
|
@ -68,11 +70,11 @@ func TestDiffProgram(t *testing.T) {
|
||||||
func TestPrinter(t *testing.T) {
|
func TestPrinter(t *testing.T) {
|
||||||
printer := Printer{}
|
printer := Printer{}
|
||||||
|
|
||||||
obj := map[string]interface{}{
|
obj := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||||
"string": "string",
|
"string": "string",
|
||||||
"list": []int{1, 2, 3},
|
"list": []int{1, 2, 3},
|
||||||
"int": 12,
|
"int": 12,
|
||||||
}
|
}}
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
printer.Print(obj, &buf)
|
printer.Print(obj, &buf)
|
||||||
want := `int: 12
|
want := `int: 12
|
||||||
|
|
Loading…
Reference in New Issue