mirror of https://github.com/kubernetes/kops.git
Let kOps take ownership of any field currently owned by kubectl
This commit is contained in:
parent
befd283381
commit
392ececda7
|
@ -167,13 +167,10 @@ func applyMenu(ctx context.Context, menu *channels.AddonMenu, k8sClient kubernet
|
|||
RESTMapper: restMapper,
|
||||
}
|
||||
|
||||
/*
|
||||
applier := &channels.ClientApplier{
|
||||
Client: dynamicClient,
|
||||
RESTMapper: restMapper,
|
||||
}
|
||||
*/
|
||||
applier := &channels.KubectlApplier{}
|
||||
applier := &channels.ClientApplier{
|
||||
Client: dynamicClient,
|
||||
RESTMapper: restMapper,
|
||||
}
|
||||
|
||||
var merr error
|
||||
|
||||
|
|
|
@ -22,8 +22,11 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
@ -103,11 +106,11 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
|||
|
||||
for i := range trackers.items {
|
||||
tracker := &trackers.items[i]
|
||||
obj := tracker.desired
|
||||
expectedObject := tracker.desired
|
||||
|
||||
name := obj.GetName()
|
||||
ns := obj.GetNamespace()
|
||||
gvk := obj.GroupVersionKind()
|
||||
name := expectedObject.GetName()
|
||||
ns := expectedObject.GetNamespace()
|
||||
gvk := expectedObject.GroupVersionKind()
|
||||
nn := types.NamespacedName{Namespace: ns, Name: name}
|
||||
|
||||
restMapping, err := a.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
|
@ -131,7 +134,7 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
|||
case meta.RESTScopeNameRoot:
|
||||
if ns != "" {
|
||||
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
||||
results.applyError(gvk, nn, fmt.Errorf("namespace %q was provided for cluster-scoped object %v", obj.GetNamespace(), gvk))
|
||||
results.applyError(gvk, nn, fmt.Errorf("namespace %q was provided for cluster-scoped object %v", expectedObject.GetNamespace(), gvk))
|
||||
continue
|
||||
}
|
||||
dynamicResource = a.client.Resource(gvr)
|
||||
|
@ -141,7 +144,24 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
|||
return nil, fmt.Errorf("unknown scope for gvk %s: %q", gvk, restMapping.Scope.Name())
|
||||
}
|
||||
|
||||
j, err := json.Marshal(obj)
|
||||
currentObj, err := dynamicResource.Get(ctx, name, v1.GetOptions{})
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("could not get existing object: %w", err)
|
||||
}
|
||||
}
|
||||
jsonData, err := createManagedFieldPatch(currentObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create json patch: %w", err)
|
||||
}
|
||||
|
||||
if jsonData != nil {
|
||||
_, err := dynamicResource.Patch(ctx, name, types.MergePatchType, jsonData, v1.PatchOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to patch object %q: %w", name, err)
|
||||
}
|
||||
}
|
||||
j, err := json.Marshal(expectedObject)
|
||||
if err != nil {
|
||||
// TODO: Differentiate between server-fixable vs client-fixable errors?
|
||||
results.applyError(gvk, nn, fmt.Errorf("failed to marshal object to JSON: %w", err))
|
||||
|
@ -161,3 +181,31 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
|
|||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func createManagedFieldPatch(currentObject *unstructured.Unstructured) ([]byte, error) {
|
||||
if currentObject == nil {
|
||||
return nil, nil
|
||||
}
|
||||
fixedManagedFields := []v1.ManagedFieldsEntry{}
|
||||
for _, managedField := range currentObject.GetManagedFields() {
|
||||
fixedManagedField := managedField.DeepCopy()
|
||||
if managedField.Manager == "kubectl-edit" || managedField.Manager == "kubectl-client-side-apply" {
|
||||
fixedManagedField.Manager = "kops"
|
||||
}
|
||||
fixedManagedFields = append(fixedManagedFields, *fixedManagedField)
|
||||
}
|
||||
if len(fixedManagedFields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
meta := &v1.ObjectMeta{}
|
||||
meta.SetManagedFields(fixedManagedFields)
|
||||
patchObject := map[string]interface{}{
|
||||
"metadata": meta,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(patchObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marsal %q into json: %w", currentObject.GetName(), err)
|
||||
}
|
||||
return jsonData, nil
|
||||
}
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 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 applyset
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kops/pkg/applylib/mocks"
|
||||
"k8s.io/kops/pkg/testutils/golden"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestApplySet(t *testing.T) {
|
||||
h := mocks.NewHarness(t)
|
||||
|
||||
existing := ``
|
||||
|
||||
apply := `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: test-applyset
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: test-applyset
|
||||
data:
|
||||
foo: bar
|
||||
`
|
||||
|
||||
h.WithObjects(h.ParseObjects(existing)...)
|
||||
|
||||
applyObjects := h.ParseObjects(apply)
|
||||
|
||||
patchOptions := metav1.PatchOptions{
|
||||
FieldManager: "kops",
|
||||
}
|
||||
|
||||
force := true
|
||||
patchOptions.Force = &force
|
||||
|
||||
s, err := New(Options{
|
||||
RESTMapper: h.RESTMapper(),
|
||||
Client: h.DynamicClient(),
|
||||
PatchOptions: patchOptions,
|
||||
})
|
||||
if err != nil {
|
||||
h.Fatalf("error building applyset object: %v", err)
|
||||
}
|
||||
|
||||
var applyableObjects []ApplyableObject
|
||||
for _, object := range applyObjects {
|
||||
applyableObjects = append(applyableObjects, object)
|
||||
}
|
||||
if err := s.SetDesiredObjects(applyableObjects); err != nil {
|
||||
h.Fatalf("failed to set desired objects: %v", err)
|
||||
}
|
||||
|
||||
results, err := s.ApplyOnce(h.Ctx)
|
||||
if err != nil {
|
||||
h.Fatalf("failed to apply objects: %v", err)
|
||||
}
|
||||
|
||||
// TODO: Implement pruning
|
||||
|
||||
if !results.AllApplied() {
|
||||
h.Fatalf("not all objects were applied")
|
||||
}
|
||||
|
||||
// TODO: Check object health status
|
||||
if !results.AllHealthy() {
|
||||
h.Fatalf("not all objects were healthy")
|
||||
}
|
||||
|
||||
var actual []string
|
||||
|
||||
for _, object := range applyObjects {
|
||||
id := types.NamespacedName{
|
||||
Namespace: object.GetNamespace(),
|
||||
Name: object.GetName(),
|
||||
}
|
||||
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetAPIVersion(object.GetAPIVersion())
|
||||
u.SetKind(object.GetKind())
|
||||
|
||||
if err := h.Client().Get(h.Ctx, id, u); err != nil {
|
||||
h.Fatalf("failed to get object %v: %v", id, err)
|
||||
}
|
||||
|
||||
metadata := u.Object["metadata"].(map[string]interface{})
|
||||
delete(metadata, "creationTimestamp")
|
||||
delete(metadata, "managedFields")
|
||||
delete(metadata, "resourceVersion")
|
||||
delete(metadata, "uid")
|
||||
|
||||
y, err := yaml.Marshal(u)
|
||||
if err != nil {
|
||||
h.Fatalf("failed to marshal object %v: %v", id, err)
|
||||
}
|
||||
actual = append(actual, string(y))
|
||||
}
|
||||
testDir := filepath.Join("testdata", strings.ToLower(t.Name()))
|
||||
golden.AssertMatchesFile(t, strings.Join(actual, "\n---\n"), filepath.Join(testDir, "expected.yaml"))
|
||||
}
|
Loading…
Reference in New Issue